背景
在java/c++中我们要实现并发编程的时候,我们通常需要自己维护一个线程池,并且需要自己去包装一个又一个的任务,同时需要自己去调度线程执行任务并维护上下文切换, 在Go语言编程中你不需要去自己写进程、线程、协程,你的技能包里只有一个技能–goroutine,当你需要让某个任务并发执行的时候,你只需要把这个任务包装成一个函数,开启一个goroutine去执行这个函数就可以了,就是这么简单粗暴。使用goroutine
Go语言中使用<font style="color:rgb(68, 68, 68);background-color:rgb(248, 248, 248);">goroutine</font>
非常简单,只需要在调用函数的时候在前面加上<font style="color:rgb(68, 68, 68);background-color:rgb(248, 248, 248);">go</font>
关键字,就可以为一个函数创建一个<font style="color:rgb(68, 68, 68);background-color:rgb(248, 248, 248);">goroutine</font>
。
一个<font style="color:rgb(68, 68, 68);background-color:rgb(248, 248, 248);">goroutine</font>
必定对应一个函数,可以创建多个<font style="color:rgb(68, 68, 68);background-color:rgb(248, 248, 248);">goroutine</font>
去执行相同的函数。
启动单个goroutine
func main() {
go hello() // 启动另外一个goroutine去执行hello函数
fmt.Println("main goroutine done!")
}
这一次的执行结果只打印了main goroutine done!
,并没有打印Hello Goroutine!
。为什么呢?
在程序启动时,Go
程序就会为main()
函数创建一个默认的goroutine
。
当main()函数返回的时候该goroutine就结束了,所有在main()函数中启动的goroutine会一同结束,main函数所在的goroutine就像是权利的游戏中的夜王,其他的goroutine都是异鬼,夜王一死它转化的那些异鬼也就全部GG了。
启动多个goroutine
我们还可以启动多个goroutine
。让我们再来一个例子: (这里使用sync.WaitGroup
来实现goroutine
的同步)
多次执行上面的代码,会发现每次打印的数字的顺序都不一致。这是因为10个
var wg sync.WaitGroup
func hello(i int) {
defer wg.Done() // goroutine结束就登记-1
fmt.Println("Hello Goroutine!", i)
}
func main() {
hellihehiii:wq
for i := 0; i < 10; i++ {
wg.Add(1) // 启动一个goroutine就登记+1
go hello(i)
}
wg.Wait() // 等待所有登记的goroutine都结束
}
<font style="color:#F5222D;background-color:rgb(248, 248, 248);">goroutine</font>
是并发执行的,而<font style="color:#F5222D;background-color:rgb(248, 248, 248);">goroutine</font>
的调度是随机的。
GO并发的实现原理
一、Go并发模型
Go实现了两种并发形式。第一种是大家普遍认知的:多线程共享内存。
其实就是Java或者C++等语言中的多线程开发。
另外一种是Go语言特有的,也是Go语言推荐的:CSP (Communication sequential process)并发模型。
:::info
CSP并发模型是在1970年左右提出的概念,属于比较新的概念,不同于传统的多线程通过共享内存来通信,CSP讲究的是“以通信的方式”来共享内存。 ::: #### 普通的线程并发模型 普通的线程并发模型, 就是像java、C++或者Python,他们线程间通信都是通过共享内存的方式来进行的。 非常典型的方式就是,在访问共享数据(例如数组、Map、或者某个结构体或对象)的时候,通过锁来访问,因此,在很多时候,衍生出一种方便操作的数据结构,叫做“线程安全的数据结构”。例如Java提供的包”java.util.concurrent”中的数据结构。Go中也实现了传统的线程并发模型。Go的CSP并发模型
Go的CSP并发模型,是通过 goroutine
和 channel
来实现的。
goroutine
是Go语言中并发的执行单位。有点抽象,其实就是和传统概念上的”线程“类似,可以理解为”线程“。<font style="color:rgb(51, 51, 51);">channel</font>
是Go语言中各个并发结构体(<font style="color:rgb(199, 37, 78);background-color:rgb(246, 246, 246);">goroutine</font>
)之前的通信机制。 通俗的讲,就是各个<font style="color:rgb(199, 37, 78);background-color:rgb(246, 246, 246);">goroutine</font>
之间通信的”管道“,有点类似于Linux中的管道。
go f()
通信机制 channel
也很方便,传数据用 channel <- data,取数据用 <-channel。
package main
import "fmt"
func main() {
messages := make(chan string)
go func() { messages <- "ping" }()
msg := <-messages
fmt.Println(msg)
}