携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情
本系列上一部分介绍了GMP模型中的一些基础概念,本部分将主要介绍调度器的一些具体策略,以及并行度控制和go中的抢占式调度。
首先是thread reuse策略,这就有点类似于“池化”的概念,具体操作是预先创建好很多资源(典型的如MySQL中的数据库连接、OS用户态中的线程),本质是避免频繁的创建、销毁线程,做到尽可能多地复用线程。
然后是work stealing机制,M在获取P与运行G时,首先尝试从某一个P的local队列中获取G,如果队列里没有G,那么M会先尝试从其他P的local队列中偷一半数量的G放到自己P的local队列。这个操作我个人感觉是比较有趣的,类似的“一半”理念还在上一部分讲到的local队列满时用到过。本质上就是让M不要闲着,尽量让M干活。
接下来是hand off机制,当M因为G进行系统调用导致阻塞时,M会释放本来已经绑定的P,把当前的P转移给其他空闲的M执行,这个感觉是协程的本质了,就是让函数通过event driven的方式,让阻塞操作不再真的阻塞,充分压榨CPU。
服务端同学在进行server程序开发时,有可能会对并发度做出一些调整,或者需要知道并发度从而对服务的抗压能力有个大概的了解,此时可以基于GOMAXPROCS这个环境变量来控制并行程度,比如设置GOMAXPROCS=CPU核心数/3,则最多有三分之一的CPU核心并行运行线程。
说到抢占原则,其实go之前是不支持抢占式的协程实现的,直到1.14版本才出现了非协作式的抢占式调度。抢占式调度设计的初衷,就是为了防止“饥饿”现象的发生。这里是以G为粒度控制时间片分配的,一个G可以拥有10ms的时间片,如果一个时间片内无法运行完成,就会被抢占。这和OS中的分时系统的基本思想有一点类似,所以可以发现,底层的设计思想和上层应用软件的思想,大部分是共通的。