golang底层 协作与抢占

抢占的时机

gc时,抢占所有g

系统监控发现长时间阻塞于系统调用或者运行时间过长时,抢占该g

 

同步协作式调度

  1. 主动用户让权:通过 runtime.Gosched 主动让出执行机会;
  2. 主动调度弃权:当发生执行栈分段时,检查自身的抢占标记,决定是否继续执行;

异步抢占式调度

  1. 被动监控抢占:当 G 阻塞在 M 上时(系统调用、channel ?等),系统监控会将 P 从 M 上抢夺并分配给其他的 M 来执行其他的 G,而位于被抢夺 P 的 M 本地调度队列中 的 G 则可能会被偷取到其他 M 中。
  2. 被动 GC 抢占:当需要进行 GC 时,为了保证不具备主动抢占处理的函数执行时间过长,导致 导致 GC 迟迟不得执行而导致的高延迟,而强制停止 G 并转为执行垃圾回收。

 

主动用户让权

可被⽤户调⽤的 runtime.Gosched 将当前 G 设为_Grunnable状态,重新放回全局队列,让出当前 M 去执⾏其他任务。⽆需对 G 做唤醒操作,因为总归会被某个 M 重新拿到,并从 “断点”恢复。实现 “断点恢复” 的关键由 mcall 实现,它将当前执⾏状态,包括 SP、PC 寄存器等值保存到 G.sched 区域。当 execute/gogo 再次执⾏该任务时,⾃然可从中恢复状态。反正执⾏栈是 G ⾃带的,不⽤担⼼执⾏数据丢失。

Gosched -> mcall(gosched_m) -> gosched_m -> goschedImpl -> dropg

                            -> globrunqput(gp)

                            -> schedule

 

主动调度弃权

g 的 stackguard0 修改为 stackPreempt,在函数调用的最前方插入抢占检测指令,进入 newstack 时,会判断是否有抢占标记,有的话,就会放弃运行。preemptone和preemptall分别用来对一个g或者全部g发出抢占请求

 

抢占P 系统调用阻塞

retake

 

抢占M 执行时间过长

retake里,P 如果是运行状态会调用 preemptone,通过系统信号来完成抢占,该调用在 M 不与 P 绑定的情况下不起任何作用直接返回。

每个运行的 M 都会设置系统信号 SIGURG 的处理函数 doSigPreempt,抢占时会调用 preemptM 向 M 发送 SIGURG 信号,操作系统会中断 M 并保护其执行现场,然后调用 doSigPreempt,doSigPreempt会修改寄存器SP和PC,当信号处理函数执行结束后,程序会再次进入内核空间,进而恢复到 被中断的位置。这个位置已经被修改过了,执行一些代码后,很快将执行调度循环。

 

posted @ 2020-05-27 22:14  是的哟  阅读(566)  评论(0编辑  收藏  举报