golang 所有进程休眠_Golang中Goroutine的调度流程
首先golang中协程golang是用户线程与系统线程的对应关系是多对多,既能利用多核cpu资源,也能尽可能减少上下文切换成本,代价是go需要实现复杂的goroutine调度机制。
N:1,所有用户线程对应1个系统线程,无法利用多核cpu;
1:1,1个用户线程对应一个系统线程,上下文切换成本高。
调度逻辑
四个结构体
M:Machine,操作系统线程。一个M被创建后会在P空闲队列中获取P进行绑定,未绑定则进入阻塞状态。
P:Process,调度器的核心处理器。通常表示执行上下文,P用于绑定M和G,由GOMAXPROCS指定最大数量,默认数量为cpu核心数。P可以跟多个M绑定,但同一时刻P只绑定在一个M上。
G:Goroutine,用户级线程。
(G0:调度函数的goroutine,go进程创建就会存在,负责调度工作。)
S:Scheduler,全局调度器,维护M、P和G队列,负责调度。
两个队列
本地队列:每个P维护一个本地队列,存放G,当前与P绑定的M若新生成G,会放入本地队列,当本地队列满时会拿出一半的本地队列中的G放入全局队列;
全局队列:存放本地队列溢出的G。调度过程中有1/61的概率检查全局队列,确保全局队列中的G也会被调度。
抢占式调度逻辑:
M绑定的P首先有1/61概率从全局队列获取G,60/61概率从本地队列获取G;
全局队列情况下如果没有获取到G,那么从本地队列获取G;
如果本地队列没有G,那么P从其他P的本地队列窃取G;
如果窃取不到G,那么从全局队列中获取一部分G到本地队列,获取n = min(len(GQ)/GOMAXPROCS 1, len(GQ/2))个;
P获取到G后,绑定的M负责执行G,M必须是运行状态的线程,否则不会真正执行。
调度本质:调度器P将协程G合理地分配到系统线程M上执行。
如果当前的M执行的G调用syscall阻塞,P会与M进行分离,M负责执行阻塞的G,P带着队列中的G绑定到新的M中,继续执行其他G;使得虽然当前G进入阻塞,但并没有影响到P去执行其他G。
M执行的G阻塞操作返回后,由于没有了P,失去切换上下文执行后续逻辑的机会,因此尝试获取新的P去执行,如果获取不到P,M就把当前G放入全局队列等待调度,自己置于休眠状态。
窃取:如果当前M绑定的P中没有可执行的G,那么就会随机从其他P中拿取一般的G放入自己的队列,以提高资源利用率。
线程自旋
线程自旋相对于线程阻塞,表现为循环执行指定的逻辑,而不进入阻塞状态。在go的调度逻辑中,为了实现高性能的并发,如果全局队列和本地队列都为空,绑定P的M没有G可以执行,会进入自旋状态等待新的G,不会进入阻塞状态休眠,减少了M的上下文切换成本。
注意只有绑定了P的M会进入自旋状态,因此最多会有GOMAXPROCS个自旋线程,避免了浪费过多系统资源,其余未绑定的空闲M依然会进入休眠状态。
您可能感兴趣的文章:
golang 所有进程休眠_Golang中Goroutine的调度流程
golang 深入浅出之 goroutine 理解
[Go 教程系列笔记] goroutine(协程)
go协程和线程的区别
go test 如何输出到控制台_深度剖析 Go 中的 Go 协程 (goroutines) -- Go 的并发
电脑升级Windows 之后无法正常关机 休眠怎么解决
让你很快就能理解-go的协程调度原理
Go:Goroutine 的切换过程实际上涉及了什么
Golang协程调度原理( G、M、P)
Golang 中的 Goroutine 调度原理与 Chanel 通信