Goroutine调度器-主动调度

161 篇文章 12 订阅
本文深入探讨了Go语言中Goroutine的主动调度机制,通过示例代码和 runtime.Gosched() 函数的分析,揭示了如何通过调用 Gosched 让当前运行的 Goroutine 主动放弃执行权,将控制权交还给调度器。文章详细介绍了从Gosched到mcall再到gosched_m的流程,展示了Goroutine如何被放入全局运行队列并进行新一轮调度。
摘要由CSDN通过智能技术生成

原文地址:Goroutine调度器-主动调度

goroutine主动调度指的是当前正在运行的goroutine通过直接调用runtime.Gosched()暂时放弃运行而发生的调度。

主动调度完全是用户代码控制的,来看下面这个例子:

package mainimport (    "runtime"    "sync")const N = 1func main() {    var wg sync.WaitGroup     wg.Add(N)    for i := 0; i < N; i++ {        go start(&wg)    }    wg.Wait()}func start(wg *sync.WaitGroup) {    for i := 0; i < 1000 * 1000 * 1000; i++ {        runtime.Gosched()    }    wg.Done()}

上述代码在main goroutine创建了一个新的称为g2的goroutine去执行start,g2在start循环中反复调用Gosched放弃执行权,主动将CPU让给调度器调度。

来看runtime/proc.go文件262行代码分析主动调度的入口函数Gosched:

// Gosched yields the processor, allowing other goroutines to run. It does not// suspend the current goroutine, so execution resumes automatically.func Gosched() {    checkTimeouts() //amd64 linux平台空函数       //切换到当前m的g0栈执行gosched_m函数    mcall(gosched_m)    //再次被调度起来则从这里开始继续运行}

因为主要看g2状态,所以用b proc.go: 266在Gosched的mcall(gosched_m)这行打个断点,之后运行程序等停止,反汇编当前正在执行的函数,得到汇编代码如下:

(gdb) disassDump of assembler code for function main.start:     0x000000000044fc90 <+0>:mov   %fs:0xfffffffffffffff8,%rcx     0x000000000044fc99 <+9>:cmp   0x10(%rcx),%rsp     0x000000000044fc9d <+13>:jbe   0x44fcfa <main.start+106>     0x000000000044fc9f <+15>:sub   $0x20,%rsp     0x000000000044fca3 <+19>:mov   %rbp,0x18(%rsp)     0x000000000044fca8 <+24>:lea   0x18(%rsp),%rbp     0x000000000044fcad <+29>:xor   %eax,%eax     0x000000000044fcaf <+31>:jmp   0x44fcd0 <main.start+64>     0x000000000044fcb1 <+33>:mov   %rax,0x10(%rsp)     0x000000000044fcb6 <+38>:nop     0x000000000044fcb7 <+39>:nop=> 0x000000000044fcb8 <+40>:lea   0x241e1(%rip),%rax        # 0x473ea0     0x000000000044fcbf <+47>:mov   %rax,(%rsp)     0x000000000044fcc3 <+51>:callq 0x447380 <runtime.mcall>     0x000000000044fcc8 <+56>:mov   0x10(%rsp),%rax     0x000000000044fccd <+61>:inc   %rax     0x000000000044fcd0 <+64>:cmp   $0x3b9aca00,%rax     0x000000000044fcd6 <+70>:jl     0x44fcb1 <main.start+33>     0x000000000044fcd8 <+72>:nop    0x000000000044fcd9 <+73>:mov   0x28(%rsp),%rax     0x000000000044fcde <+78>:mov   %rax,(%rsp)     0x000000000044fce2 <+82>:movq   $0xffffffffffffffff,0x8(%rsp)     0x000000000044fceb <+91>:callq 0x44f8f0 <sync.(*WaitGroup).Add>     0x000000000044fcf0 <+96>:mov   0x18(%rsp),%rbp     0x000000000044fcf5 <+101>:add   $0x20,%rsp     0x000000000044fcf9 <+105>:retq        0x000000000044fcfa <+106>:callq 0x447550 <runtime.morestack_noctxt>     0x000000000044fcff <+111>:jmp   0x44fc90 <main.start>

当前正在执行main.start而不是runtime.Gosched,在整个start中都没有Gosched,它被编译器优化掉了,程序停0x000000000044fcb8 <+40>: lea 0x241e1(%rip),%rax处,该指令下第二条callq调用runtime.mcall,接下来使用si 2执行这两条指令,之后使用i r rsp rbp rip记录CPU的rsp、rbp、rip的值,如下:

(gdb) i r rsp rbp riprsp    0xc000031fb0     0xc000031fb0rbp    0xc000031fc8     0xc000031fc8rip     0x44fcc3        0x44fcc3 <main.start+51>

0x000000000044fcc3处的callq先将其后首条指令地址0x000000000044fcc8放入g2的栈,之后跳到mcall首条指令处开始执行。

mcall将完成如下工作:

  1. 将上面call压栈的返回地址0x000000000044fcc8取出并保存在g2的sched.pc,将上面查到的rsp(0xc000031fb0)和rbp(0xc000031fc8)的值分别保存在sched.sp和sched.bp,代表g2的调度现场信息。

  2. 将保存在g0的sched.sp和sched.bp的值分别恢复到CPU的rsp和rbp,完成从g2的栈到g0的栈的切换。

  3. 在g0栈执行gosched_m(runtime.Gosched调用mcall传给mcall的参数)。

来看runtime/proc.go文件2623行代码分析gosched_m:

// Gosched continuation on g0.func gosched_m(gp *g) {    if trace.enabled { //traceback 不关注        traceGoSched()    }    goschedImpl(gp)  //我们这个场景:gp = g2}

gosched_m只是简单调用goschedImpl,来看runtime/proc.go文件2608行:

func goschedImpl(gp *g) {    ......    casgstatus(gp, _Grunning, _Grunnable)    dropg() //设置当前m.curg = nil, gp.m = nil    lock(&sched.lock)    globrunqput(gp) //把gp放入sched的全局运行队列runq    unlock(&sched.lock)    schedule() //进入新一轮调度}

goschedImpl有一g指针类型的形参,此场景传给它的实参是g2,goschedImpl先将g2状态从_Grunning设为_Grunnable,并通过dropg解除当前工作线程m与g2间的关联(m.curg设为nil,g2.m设为nil),之后调globrunqput将g2放入全局运行队列。

g2放入全局运行队列后,g2及其它部分状态如下:

可以看到,g2被放入sched的全局运行队列,该队列有个head头指针指向队列中首个g对象,还有tail尾指针指向队列最后一个g对象,g2的sched保存了调度所需全部现场信息,当g2再次被schedule调度时,gogo将信息恢复到CPU的rsp、rbp、rip中,使g2又可从0x44fcc8处开始在g2栈中执行g2代码。

将g2放入sched的全局运行队列后,goschedImpl继续调schedule进入下轮调度循环。

至此,g2通过自主调Gosched,实现主动调度。

以上仅为个人观点,不一定准确,能帮到各位那是最好的。

好啦,到这里本文就结束了,喜欢的话就来个三连击吧。

扫码关注公众号,获取更多优质内容。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

luyaran

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

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

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

打赏作者

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

抵扣说明:

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

余额充值