当我们在go func{}下有如何代码的时候,Go是如何处理阻塞的?
注:阻塞情况还有其它情况比如 加锁,网络连接读/写,select
我们需要知道在Go中代码的执行是需要线程(M)绑定 P 才能在CPU上执行
如果不知道什么是GMP,可以先阅读
在Go中,这种情况是不会阻塞调度的,而是会把goroutin挂起
所谓挂起,就是让G进入某个数据结构,待ready后再继续执行,不会占用线程
线程会进入schedule,继续消费队列,执行其它的G
什么是schedule?如下图(具体还是看👆🏻的文章【GMP】当我写下go func的时候,到底发生了什么? )
上述图是Go中各种阻塞的情况下的处理
以channel举例,对应上图左上角的hchan
- type hchan struct {
- ……
- sendq waitq // list of send waiters
- ……
- }
我们来看下在runtime中chan.go的hchan的数据结构
…… 代表省略了部分结构成员
sendq是一个waitq类型
- type waitq struct {
- first *sudog
- last *sudog
- }
first和last都是一个sudog类型指针
Go就是把这些阻塞的G或者sudog挂载到这些数据结构里面
我们之前说的都是G还有,那sudog是什么?他们之间有什么关系吗?
一个G可能会对应多个sudog,比如一个G会同时select多个channel
sysmon是什么?我们先来了解一下Go里面的线程
我们在上述图可以看到线程的职责
gsignal执行信号处理时,切换到gsignal栈
G0执行runtime调度代码时,切换到m.g0(就是主线程,绿色的那个)
G 执行用户逻辑时的状态
而他们都是要绑定P才能执行的,而不需要P就可以执行
废话一大堆,那sysmon有什么用呢?
在这篇文章的sysmon之前所谈到的阻塞都是可接管的阻塞
那也有不可接管的阻塞,比如syscall(系统调用),长时间运行需要剥离P执行
比如程序中写了如下代码
GC时需要停止所有goroutine,而老版本的Go的g停止需要主动让出
什么是主动让出?GO协程(goroutine)的使用
如果不主动让出,那么GC时就hang死了,
那么在Go 1.14增加了基于信号的抢占之后,该问题被解决了
那这个信号的抢占就是由sysmon负责
上述图片是sysmon的职责
如果有G运行超过10ms,或者syscall阻塞超过10ms那么久会执行retake操作进行剥离
本文完~