0
点赞
收藏
分享

微信扫一扫

Go语言的并发编程

海滨公园 2022-02-03 阅读 33

Go语言里的并发指的是能让某个函数独立于其他函数运行的能力。当一个函数创建为协程(goroutine)时,Go语言会将其视为一个独立的工作单元,这个单元会被调度到可用的逻辑处理器上执行。

并发编程基础

1、并发与并行(理论没看懂)

理解操作系统的线程(thread)和进程(process),有助于理解Go语言运行时调度器如何得用操作系统来并发运行goroutine。

并行是让不同的代码片段同时在不同的物理处理器上执行。并行的关键是同时做很多事情,而并发是指同时管理很多事情,这些事情可能只做了一半就被暂停去做别的事情了。

使用较少的资源做更多的事情,是指导Go语言设计的哲学。

2、指定使用核心数

Go语言默认会调用CPU核心数,这些功能都是自动调整的,但是也提供了相应的标准库来指定核心数。使用flags包可以调整程序运行时调用的CPU核心数。

代码如下:

package main

import (
"fmt"
"time"
)

func longWait() {
fmt.Println("开始longWait()")
time.Sleep(5 * 1e9)
fmt.Println("结束longWait()")
}

func shortWait() {
fmt.Println("开始shortWait()")
time.Sleep(2 * 1e9)
fmt.Println("结束shortWait()")
}

func main() {
fmt.Println("这里是main()开始的地方")
go longWait()
go shortWait()
fmt.Println("挂起main()")
time.Sleep(10 * 1e9)
fmt.Println("这里是main()结束的地方")
}

main()、longWait()和shortWait()三个函数作为独立的处理单元按顺序启动,然后开始并行运行,每一个函数都在运行的开始和结束阶段输出了消息。

运行结果如下:

这里是main()开始的地方
挂起main()
开始longWait()
开始shortWait()
结束shortWait()
结束longWait()
这里是main()结束的地方

如果移除go语言关键字同,重新运行程序:

这里是main()开始的地方
开始longWait()
结束longWait()
开始shortWait()
结束shortWait()
挂起main()
这里是main()结束的地方

协程(goroutine)

操作系统自己掌管的进程(process)、进程内的线程(thread)以及进程内的协程(coroutine,也叫轻量级线程)。

协程的最大优势在于其“轻量级”,可以轻松创建上百万个协程而不会导致系统资源衰竭,而线程和进程通常最多也不能超过1万个。

Go语言在语言级别支持轻量级线程,叫goroutine。

1、协程基础

goroutine是Go语言并行设计的核心。

goroutine是通过Go程序的runtime管理的一个线程管理器。goroutine通过go关键字实现了,其实就是一个普通的函数。

代码如下:

package main

import (
"fmt"
"runtime"
)

func say(s string) {
for i := 0; i < 5; i++ {
runtime.Gosched()
fmt.Println(s)
}
}

func main() {
go say("world")
say("hello")
}

运行结果如下:与书本不一样。

hello
hello
hello
hello
hello

书本是:

hello
world
hello
world
hello
world
hello
world
hello

2、协程间通信

通常有两种最常见的并发通信模型:共享数据和消息。

代码如下:

package main

import (
"fmt"
"runtime"
"sync"
)

var counter int = 0

func Count(lock *sync.Mutex) {
lock.Lock()
counter++
fmt.Println(z)
lock.Unlock()
}

func main() {
lock := &sync.Mutex{}
for i := 0; i < 10; i++ {
go Count(lock)
}
for {
lock.Lock()
c := counter
lock.Unlock()
runtime.Gosched()
if c >= 10 {
break
}
}
}

运行结果如下:

# command-line-arguments
./main.go:14:14: undefined: z

不要通过共享内存来通信,而应该通过通信来共享内存。

通道(Channel)

代码如下:

package main

import (
"fmt"
)

func Count(ch chan int) {
ch <- 1
fmt.Println("Counting")
}

func main() {
chs := make([]chan int, 10)
for i := 0; i < 10; i++ {
chs[i] = make(chan int)
go Count(chs[i])
}
for _, ch := range chs {
<-ch
}
}

1、基本语法

一般channel的声明形式为:

在channel的用法中,最常见的包括写入和读出。

将一个数据写入(发送)到channel的语法很直观:

从channel中读取数据的语法是:

2、select

Go语言直接在语言级别支持select关键字,用于处理异步I/O问题。

3、缓冲机制

4、超时和计时器

5、channel的传递

6、单向channel

7、关闭channel

使用go语言内置的close()函数如下:

close(ch)

并发进阶

1、多核并行化

2、协程同步

只有在当需要告诉接受者不会再提供新的值的时候,才需要关闭通道。

只有发送者需要关闭通道,接收都永远不会需要。

代码如下:

package main

import (
"fmt"
"time"
)

func sendData(ch chan string) {
ch <- "纽约"
ch <- "华盛顿"
ch <- "伦敦"
ch <- "北京"
ch <- "东京"
}

func getData(ch chan string) {
var input string
for {
input = <-ch
fmt.Printf("%s ", input)
}
}

func main() {
ch := make(chan string)
go sendData(ch)
go getData(ch)
time.Sleep(1e9)
}

运行结果如下:

纽约 华盛顿 伦敦 北京 东京 

代码如下:

package main

import (
"fmt"
)

func sendData(ch chan string) {
ch <- "纽约"
ch <- "华盛顿"
ch <- "伦敦"
ch <- "北京"
ch <- "东京"
close(ch)
}

func getData(ch chan string) {
for {
input, open := <-ch
if !open {
break
}
fmt.Printf("%s ", input)
}
}

func main() {
ch := make(chan string)
go sendData(ch)
getData(ch)
}

运行结果如下:

纽约 华盛顿 伦敦 北京 东京 

举报

相关推荐

0 条评论