协程goroutine

* 本页面主要介绍Go语言协程goroutine的相关内容。

协程goroutine是Go语言实现并发的“核心武器”,上一节我们讲解高并发原理时知道,goroutine类似于线程的概念,但是goroutine是由Go的运行时(runtime)调度和管理的。Go程序会智能地将 goroutine 中的任务合理地分配给每个CPU。程序员在编写并发程序是并不需要自己去写“调度”相关的部分,如果需要让任务并发执行,你只需要把这个任务包装成一个函数,然后开启一个goroutine去让他执行就好了,就是这么简单。

当一个程序启动时,其主函数即在一个单独的goroutine中运行,我们叫它main goroutine。那么其他的goroutine要怎么启动呢?接着往下看!

一、goroutine的使用

Go语言中使用goroutine非常简单,只需要在调用函数的时候在前面加上go关键字,就可以为一个函数创建一个goroutine。

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

但你会发现,上面程序运行后只输出了一个"main goroutine done!",Why?因为还没等go出来的goroutine执行或者执行完,main goroutine就执行完结束了!只要main goroutine结束,其中创建的所有子goroutine都会随之消亡,无论执行到什么节点。所以,怎么能够让子goroutine执行完在结束呢?

最简单粗暴的方式是time.Sleep。

  func main() {
      go hello() // 启动另外一个goroutine去执行hello函数
      fmt.Println("main goroutine done!")
      time.Sleep(time.Second)
  }

这一次先打印main goroutine done!,然后紧接着打印Hello Goroutine!。因为创建一个goroutine也需要时间开销,虽然很小。

启动多个goroutine
  var wg sync.WaitGroup

  func hello(i int) {
      defer wg.Done() // goroutine结束就登记-1
      fmt.Println("Hello Goroutine!", i)
  }
  func main() {

      for i := 0; i < 10; i++ {
          wg.Add(1) // 启动一个goroutine就登记+1
          go hello(i)
      }
      wg.Wait() // 等待所有登记的goroutine都结束
  }

这里使用了sync.WaitGroup来实现goroutine的同步,你会发现每次执行打印数字的顺序不一样,因为10个goroutine是并发执行的,而goroutine的调度是随机的。

二、goroutine和线程

二者的区别在 高并发原理 一节有介绍,这里简单说一下。

OS线程(操作系统线程)一般都有固定的栈内存(通常为2MB),一个goroutine的栈在其生命周期开始时只有很小的栈(典型情况下2KB),goroutine的栈不是固定的,他可以按需增大和缩小,goroutine的栈大小限制可以达到1GB,虽然极少会用到这个大。所以在Go语言中一次创建十万左右的goroutine也是可以的。

三、goroutine调度

G-P-M调度模型在 高并发原理 一节有介绍,这里不做赘述。

四、goroutine泄露

goroutine如果使用不规范,容易造成泄露,进而影响程序稳定性。相关知识下一次总结。


* 本页内容参考以下数据源:

  • 《Go程序设计语言》
  • https://zhuanlan.zhihu.com/p/74090074

凯冰科技 · 代码改变世界,技术改变生活
下一篇:通道channel →