背景

在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

  1. func main() {
  2. go hello() // 启动另外一个goroutine去执行hello函数
  3. fmt.Println("main goroutine done!")
  4. }

这一次的执行结果只打印了main goroutine done!,并没有打印Hello Goroutine!。为什么呢?

在程序启动时,Go程序就会为main()函数创建一个默认的goroutine

当main()函数返回的时候该goroutine就结束了,所有在main()函数中启动的goroutine会一同结束,main函数所在的goroutine就像是权利的游戏中的夜王,其他的goroutine都是异鬼,夜王一死它转化的那些异鬼也就全部GG了。

启动多个goroutine

我们还可以启动多个goroutine。让我们再来一个例子: (这里使用sync.WaitGroup来实现goroutine的同步)

  1. var wg sync.WaitGroup
  2. func hello(i int) {
  3. defer wg.Done() // goroutine结束就登记-1
  4. fmt.Println("Hello Goroutine!", i)
  5. }
  6. func main() {
  7. hellihehiii:wq
  8. for i := 0; i < 10; i++ {
  9. wg.Add(1) // 启动一个goroutine就登记+1
  10. go hello(i)
  11. }
  12. wg.Wait() // 等待所有登记的goroutine都结束
  13. }
多次执行上面的代码,会发现每次打印的数字的顺序都不一致。这是因为10个<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并发模型,是通过 goroutinechannel 来实现的。

  • 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中的管道。
生成一个goroutine的方式非常的简单:Go一下,就生成了。
  1. go f()

通信机制 channel 也很方便,传数据用 channel <- data,取数据用 <-channel

在通信过程中,传数据channel <- data和取数据<-channel必然会成对出现,因为这边传,那边取,两个goroutine之间才会实现通信。 而且不管传还是取,必阻塞,直到另外的goroutine传或者取为止。
  1. package main
  2. import "fmt"
  3. func main() {
  4. messages := make(chan string)
  5. go func() { messages <- "ping" }()
  6. msg := <-messages
  7. fmt.Println(msg)
  8. }

参考资料