Go调度器的工作是在一个或多个处理器上运行的多条系统线程分配可运行的goroutines。 在多线程计算中,调度中出现了两个范例:工作共享和工作窃取。
- 工作共享:当一个处理器生成新线程时,它会尝试将它其中一些线程迁移到其他处理器上,以希望它们利用被闲置或未充分利用的处理器。
- 工作窃取:未充分利用的处理器会积极寻找其他处理器的线程并“窃取”部分。
工作窃取比工作共享发生的线程迁移频率更低。在工作共享模式种,当所有处理器都有任务运行时,不会迁移任何线程。 并且,一旦有一个空闲的处理器,就认为要迁移线程了。 Go从1.1开始就使用工作窃取调取了,是由Dmitry Vyukov贡献的。 本文将深入解释窃取工作调度是什么以及Go如何实现它。
调度的基本模型
Go有一个可以利用多个处理器的M:N模型调度器。 在任何时候,都需要在最多可运行GOMAXPROCS个处理器的N个OS线程上调度M个goroutine。 Go调度器为goroutine,线程和处理器使用了以下术语:
- G: goroutine
- M: OS thread (machine)
- P: processor
这里有一个特定的P的本地和全局goroutine队列。 每个M应该分配给一个P。Ps这时候可能没有Ms如果Ms被阻塞或处于系统调用中。 在任何时候,最多有GOMAXPROCS个P。在任何时候,每个P只能运行一个M。如果需要,调度程序可以创建更多M。
每一轮调度都只是找到一个可运行的goroutine并执行它。 在每一轮调度中,搜索均按以下顺序进行:
runtime.schedule() {
// only 1/61 of the time, check the global runnable queue for a G.
// if not found, check the local queue.
// if not found,
//try to steal from other Ps.
//if not, check the global runnable queue.
//if not found, poll network.
}
一旦找到可运行的G,它将一直执行直到被阻塞。 注意:看起来全局队列比本地队列更优先,但是每隔一段时间检查一次全局队列是至关重要的,以避免M仅从本地队列进行调度直到没有本地排队的goroutine。
窃取
当创建一个新的G时或者一个存在的G变为可运行状态,它将被放到当前P的可运行goroutine列表中。当P完成执行G时,它将尝试从自己的可运行goroutine列表中弹出一个G。 如果列表此刻为空,则P随机选择一个的其他处理器(P)并尝试从其队列中窃取一半可运行的goroutine。
在上述例子中,P2无法找到任何可运行的goroutine。 因此,它随机选择另一个处理器(P1)并将三个goroutine窃取到其自己的本地队列中。 P2将能够运行这些goroutine,并且调度程序的工作将更加公平地分布在多个处理器之间。
旋转线程
调度器始终希望向Ms分发尽可能多的可运行goroutine以充分利用处理器,但是与此同时,我们需要挂起过多的工作以节省CPU和功耗。 与此矛盾的是,调度器应该需要能够扩展到高吞吐量和CPU密集型程序。
对于高吞吐量程序如果性能很重要的话不断抢占既昂贵也是一个问题。 系统线程不应经常在彼此之间切换可运行的goroutine,因为这会导致增加延迟。 除此之外,存在系统调用时,系统线程需要不断阻塞和解除阻塞OS线程。 这既昂贵又增加了很多开销。
为了最大程度地减少切换,Go调度程序实现了“旋转线程”。 旋转线程会消耗一点额外的CPU能力,但会最大程度地减少操作系统线程的抢占。 如果发生以下情况,旋转线程:
- 分配了P的M正在寻找可运行的goroutine。
- 没有分配P的M正在寻找可用的P。
- 如果有一个空闲的P并且没有其他线程旋转,则调度器还会准备一个goroutine唤醒一个额外的线程并对其进行旋转。
任何时刻最多有GOMAXPROCS个旋转的M。 当旋转的线程找到执行的任务时,它将退出旋转状态。如果存在没有分配P的空闲M,则分配了P的空闲线程不会阻塞。 当创建新的goroutine或阻塞一个M时,调度程序将确保至少有一个旋转的M。这确保了没有可以运行的goroutine(可以被其他运行的); 并避免过多的M阻塞/解除阻塞。
Go调度器通过偷取将它们调度到正确的处理器和未充分利用的处理器上大量避免了过的系统线程的抢占,以及实现“旋转”线程来避免发生过多的阻塞/解除阻塞的转换。
总结
Go调度器通过窃取将它们调度到正确的和未充分利用的处理器上大量避免了系统线程的抢占,以及实现“旋转”线程来避免发生过多的阻塞/解除阻塞的切换。调度事件可以由execution tracer跟踪。如果你认为你的处理器利用率很低你也可以研究背后到底发生了什么。