[Golang记录] (1) Go Scheduler

Go Scheduler

本文是学习博客(原文链接)后,结合自己的理解整理出的文章。记录下来,大家共同学习,如有问题点,欢迎讨论。

一、概念介绍

逻辑处理器-P

Go程序启动时会为当前主机上每虚拟内核分配一个逻辑处理器(P)
(runtime.NumCPU())。比如4核物理处理器,每个物理内核有两个硬件线程,Go程序最终会分配8个逻辑处理器并行执行OS线程。

OS线程-M

每个逻辑处理器-P会被分配一个OS线程-M,受OS管理

Goroutine-G

本质为Coroutine,golang中称Goroutine,每个Go程序会有一个初始Goroutine.类比到OS线程在内核中进行上下文切换,Goroutine在M上进行上下文切换.

运行队列:GRQ & LRQ

Go Scheduler中有两个不同的队列:全局运行队列(GRQ)和本地运行队列(LRQ)。
GRQ(Global Run Queue):GRQ用于存储未分配到P的Goroutine.
LRQ(Local Run Queue):每个P都有一个LRQ用于管理分配给P的Goroutine.

关系图

二、调度器

Go调度程序是Go Runtime的一部分,Go运行时内置于应用程序中。这意味着Go调度程序在内核之上的用户空间中运行。

三、Goroutine状态

Goroutine包含三种状态:Waiting、Runnable、Executing。
Waiting:Goroutine停止并等待。可能是由于操作系统-系统调用或同步调用-原子和互斥操作等原因。
Runnable:处于队列中,Goroutine等待分配M执行。
Executing:意味着Goroutine已经分配到M上并正在执行。

四、调度决策

1、异步系统调用

当操作系统拥有异步处理系统调用的能力时,可以使用network poller来更有效的处理基于网络的系统调用。
通过在各个操作系统中使用 kqueue (MacOS)、epoll (Linux) 或 iocp (Windows) 来完成的。

network poller 见名知意,主要用来处理基于网络的系统调用。在Go调度程序可以防止Goroutine在进行网络调用时阻塞M,可以让M执行P的LRQ中的其他Goroutine,而无需新建M,减少OS调度。
异步系统调用-调度图1
调度图1中,network poller处于空闲状态,G1正在M上执行,还有3个Goroutine在LRQ中等待获取M的时间片。
继续看下面的调度图2
异步系统调用-调度图2
调度图2中,G1想要执行网络系统调用,因此将G1移动至network poller中进行异步网络系统调用,M可以用来执行LRQ中其它的Goroutine。此时,LRQ中的G2获取到M的时间片,G2在M上完成了上下文切换。

异步系统调用-调度图3
调度图3中,network poller完成了G1异步网络系统调用,并将G1移回P的LRQ中等待下一次获取到M时间片,执行相应go程序,而不需要额外的M。

2、同步系统调用

当Goroutine想要进行同步系统调用,这时无法使用network poller,并且进行系统调用的Goroutine将会阻塞M。
下面让我们看看会导致M阻塞的同步系统调用(如文件I/O)会发生什么。
同步系统调用-调度图1
调度图-1中,G1降会进行同步系统调用,并会阻塞M。
同步系统调用-调度图-2
调度图-2中,调度程序识别出G1导致M阻塞。此时,调度程序将M1从P中分离出来,而阻塞的G1仍然连接M1。然后调度器为P引入一个新的M2。因此,可以选择LRQ中的G2并在M2上进行上下文切换。如果因为之前的交换已经存在M,则此次转换比新创建M更快。
同步系统调用-调度图-3
调度图-3中,由G1阻塞的系统调用完成后,G1会被调度程序移回LRQ等待M2分配时间片。同时M1会被保留,以备将来使用。

3、Work Stealing

Go调度器还是一个Work Strealing Scheduler。
两方面原因:

  1. 提高调度效率,避免M进入等待状态,从而导致操作系统将M上下文切换到内核之外。如果存在可运行的Goroutine,P需要等待M获取时间片后才能执行。
  2. 平衡多个P之间的Goroutines.

如下示例
Work Stealing 调度图-1
调度图-1中,一个多线程go程序,其中两个P分别负责4个Goroutine(M上+LRQ中),GRQ中还有1个Goroutine。如果两个P的处理速度不同,会发生什么?
Work Stealing 调度图-2
调度图-2中,P1没有更多的Goroutine可以执行,但是在GRQ和P2的LRQ中都有处于可运行状态的Goroutine。这时P1会窃取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.
}

根据代码段中的规则,P1 需要check P2 的 LRQ 中的 Goroutines,并取其一半的Goroutines。
Work Stealing 调度图-3
调度图-3中,一半的 Goroutines 取自 P2,现在 P1 可以执行这些 Goroutines。
如果 P2 完成了所有 Goroutines 的服务,并且 P1 的 LRQ 中没有任何Goroutines,此时会发生什么?
Work Stealing 调度图-4
在调度图-4中,P2完成了所有Goroutine工作,需要窃取Goroutine。先查询P1的LRQ,但找不到Goroutine。然后会查看GRQ,窃取到G9。
Work Stealing 调度图-5
调度图-5中,P2 从 GRQ 中窃取了G9并开始执行工作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dnice

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值