
上QQ阅读APP看书,第一时间看更新
1.1.5 被废弃的Goroutine调度器
现在知道了协程和线程的关系,那么最关键的一点就是调度协程的调度器实现了。Go语言目前使用的调度器是2012年重新设计的,因为之前的调度器性能存在问题,所以使用4年就被废弃了,那么先来分析一下被废弃的调度器是如何运作的。通常用符号G表示Goroutine,用M表示线程,如图1.8所示。接下来有关调度器的内容均采用图1.8所示的符号来统一表达。

图1.8 G、M符号表示
早期的调度器是基于M:N的基础上实现的,图1.9是一个概要图形,所有的协程,也就是G都会被放在一个全局的Go协程队列中,在全局队列的外面由于是多个M的共享资源,所以会加上一个用于同步及互斥作用的锁。
M想要执行、放回G都必须访问全局G队列,并且M有多个,即多线程访问同一资源需要加锁进行保证互斥/同步,所以全局G队列是由互斥锁进行保护的。
不难分析出来,老调度器有以下几个缺点:
(1)创建、销毁、调度G都需要每个M获取锁,这就形成了激烈的锁竞争。
(2)M转移G会造成延迟和额外的系统负载。例如当G中包含创建新协程的时候,M创建了G′,为了继续执行G,需要把G′交给M2(假如被分配到)执行,也造成了很差的局部性,因为G′和G是相关的,最好放在M上执行,而不是其他M2,如图1.10所示。

图1.9 Go语言早期调度器的处理

图1.10 Go语言早期调度器的局部性问题
(3)系统调用(CPU在M之间的切换)导致频繁的线程阻塞和取消阻塞操作增加了系统开销。