go协程的调度方式【】

先看一段代码:

package main

import (
        "fmt"
        "runtime"
        "time"
)

func main() {
        runtime.GOMAXPROCS(1)
        go func() {
                for  {

                }
        }()
        time.Sleep(time.Millisecond)
        fmt.Println("finished.")
}

这段代码在go1.13中运行是死循环,在go1.17中运行输出finished然后退出。

go的调度有以下几种方式:

1.主动调度

自己主动放弃执行,通过runtime.Gosched()实现。取消G与M之间的绑定关系,将G放入到全局运行对列中去。

2.被动调度

这个主要发生在sleep,channal堵塞,垃圾回收等情况。通过调用gopark函数完成别动调度。该函数会解除G与M之间的关系,然后根据被动调度的原因不同,执行不同的waitunlockf函数,并开始新一轮的执行。

3.抢占调度

主要抢占执行时间过长的G和处于系统调用阶段的G。

抢占调度依赖一个监控线程,该线程在独立的M上运行,不需要绑定逻辑处理器P。

1)抢占执行时间过长的G,发生在函数调用的时候。代码编译的时候,编译器会在函数调用前加入一些检测代码。当该G执行时间超过10ms时,监控线程会对这段检测代码中的某些标志位做设置,当发生函数调用时,检测到这些设置,发生重新调度。

2)抢占系统调用。当G在系统调用中超过10ms时,且任务比较繁忙时,将发生抢占调度(任务不忙时,抢占没有意义,任务繁忙的判断有一些具体的标准,该处省略)。抢占原理是将被系统调用线程M占用的P让出。该P让出后,可以与其他M绑定执行别的任务。

在1.13版本及之前大概就这么多。1.14开始有变化。

主要变化发生在 抢占调度 中存在问题。当cpu密集型任务执行时,可能没有发生函数调用,也没有系统调用,这样就没法进行抢占。 优化的目的就是要想办法打断G的执行,然后执行调度。这里采取的是通过信号来进行中断的方式。

监控线程检测到某个G执行时间过长,就发送信号_SIGURG,然后程序在处理信号时,修改了原程序中某些寄存器的值,使得程序继续执行的方向发生了改变。也就是当信号中断处理结束后,程序恢复运行的时候,不会再继续执行了,而是去执行新一轮的调度函数。

文章来源于互联网:go协程的调度方式

打赏 赞(1) 分享'
分享到...
微信
支付宝
微信二维码图片

微信扫描二维码打赏

支付宝二维码图片

支付宝扫描二维码打赏