1. goroutine什么时候被挂起

  • network I/O阻塞(实际上golang已经用netpoller实现了goroutine网络I/O阻塞不会导致M被阻塞,仅阻塞G)
  • channel 信道阻塞(select)
  • 等待锁,对同一个mutex,当goroutine A调用了mutex.Lock()后,gorontine B再调用mutex.Lock()时会挂起,直到goroutine A调用mutex.Unlock()释放掉锁资源后,goroutine B才会执行mutex.Lock()继续往下执行
  • sync.WatiGroup()

goroutine挂起后被放置到某一个wait队列(比如channel的waitq),该G的状态由_Grunning变成 _Grwatting

2. 什么是goroutine泄露

当你启动一个goroutine,但没有按照预期一样的退出,直到程序结束,此gorroutine才结束。这种情况就是goroutine结束。当goroutine泄露发生的时候,该goroutine的栈一直被占用而不能释放,goroutine里的函数在堆上申请的空间也不能

被垃圾回收器回收。这样,在程序运行期间,内存占用持续升高。

大多数情况下,引起goroutine泄露的原因有两类:channel阻塞;goroutine陷入死循环。

  • 向已满的channel里写,但是没有读
  • 从channel里读,但是没有写
  • 死循环当代码里循环的退出条件不可达时,会令该goroutine进入死循环中,进而导致资源一直无法释放,引起泄露。在实际项目中,往往死循环会发生在一些后台的常驻服务中。

3. goroutine泄露预防和检测

预防

  • 最重要的一点,在创建goroutine时,就应该知道goroutine啥时能结束。
  • channel引起的goroutine泄露问题,主要是在channel阻塞goroutine时,该goroutine的阻塞是正常的,还是可能导致协程永远没有机会执行。若是后者,则极大可能会造成协程泄露。
  • channel的实际使用中,常用的两种模型:生产者-消费者模型;master-worker模型。一般的解决方案是:当主线程结束时,告知生产协程,生产协程得到通知后,进行清理工作然后退出;为每个worker任务制定超时,当超时触发,返回给master超时信息,并结束该worker协程。具体代码方案是使用上下文context。
  • 实现循环语句时必须清晰地知道退出循环的条件,避免死循环。

检测

  • Go提供的pprof工具。
  • 利用runtime.NumGoroutine函数,实时查看程序中运行的goroutine数。

4. CSP模型

CSP并发模型
CSP 经常被认为是 Go 在并发编程上成功的关键因素,CSP模型是上个世纪七十年代提出的,用于描述两个独立的并发实体通过共享的通讯 channel(管道)进行通信的并发模型。 CSP中channel是第一类对象,它不关注发送消息的实体,而关注与发送消息时使用的channel。

Golang中使用 CSP中 channel 这个概念。channel 是被单独创建并且可以在进程之间传递,它的通信模式类似于 boss-worker 模式的,一个实体通过将消息发送到channel 中,然后又监听这个 channel 的实体处理,两个实体之间是匿名的,这个就实现实体中间的解耦,其中 channel 是同步的,一个消息被发送到 channel 中,最终是一定要被另外的实体消费掉的,在实现原理上其实是一个阻塞的消息队列。

Goroutine 是实际并发执行的实体,它底层是使用协程(coroutine)实现并发,coroutine是一种运行在用户态的用户线程,类似于 greenthread,go底层选择使用coroutine的出发点是因为,它具有以下特点:

  • 用户空间 避免了内核态和用户态的切换导致的成本
  • 可以由语言和框架层进行调度
  • 更小的栈空间允许创建大量的实例

Golang为了调度的公平性,在调度器加入了steal working 算法 和hand off机制(P和M解绑)。stealing work:在一个P自己的执行队列,处理完之后,它会先到全局的执行队列中偷G进行处理,如果没有的话,再会到其他P的执行队列中抢G来进行处理。hand off若G进行系统调用时候,会阻塞M,此时P会和M解绑(即hand off),并寻找新的空闲的M。若没有空闲的就会创建一个新的M。

Golang实现了 CSP 并发模型做为并发基础,底层使用goroutine做为并发实体,goroutine非常轻量级可以创建几十万个实体。实体间通过 channel 继续匿名消息传递使之解耦,在语言层面实现了自动调度,这样屏蔽了很多内部细节,对外提供简单的语法关键字,大大简化了并发编程的思维转换和管理线程的复杂性。