返回信息流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然后退出,为什么?
网上文章说“如果正在执行的G是个很耗时的操作且没有任何函数调用(如只是for循环中的计算操作),即使抢占flag已经被设置,该G还是将一直霸占着当前M直到执行完自己的任务。” 上面例子中死循环协程中并没有函数调用,为什么也发生了切换?
https://github.com/k2huang/blogpost/blob/master/golang/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/%E5%B9%B6%E5%8F%91%E6%9C%BA%E5%88%B6/Go%E5%B9%B6%E5%8F%91%E6%9C%BA%E5%88%B6.md
这是一条镜像帖。来源:北邮人论坛 / golang / #2330同步于 2022/2/20
该镜像源已超过 30 天没有更新,可能在源站已被删除。
Golang机器人发帖
go的协程调度问题
tiaoji
2022/2/20镜像同步8 回复
订阅后,新回复会通过你的通知中心匿名送达。
8 条回复
https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-goroutine/#抢占式调度器
简单地来讲就是1.13之前是基于协作的抢占式调度,只有在发生函数调用时才进行抢占
1.14之后改成基于信号的抢占式调度,会在gc时触发抢占
【 在 tiaoji 的大作中提到: 】
: package main
: import (
: ............
推荐B站幼麟实验室的抢占式调度详解,跟着源码的画图讲解
https://www.bilibili.com/video/BV1kL411p7mW?spm_id_from=444.41.0.0
我自己弄明白了。
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时,且任务比较繁忙时,将发生抢占调度(任务不忙时,抢占没有意义,任务繁忙的判断有一些具体的标准,该处省略)。抢占原理时将P让出。该P让出后,可以与其他M绑定执行别的任务。
在1.13版本及之前大概就这么多。1.14开始有变化。
主要变化发生在 3.抢占调度 中存在问题。当cpu密集型任务执行时,可能没有发生函数调用,也没有系统调用,这样就没法进行抢占。 优化的目的就是要想办法打断G的执行,然后执行调度。这里采取的时通过信号来进行中断的方式。
监控线程检测到某个G执行时间过长,就发送信号_SIGURG,然后程序在处理信号时,修改了原程序中某些寄存器的值,使得程序继续执行的方向发生了改变。也就是当信号中断处理结束后,程序恢复运行的时候,不会再继续执行了,而是去执行新一轮的调度函数。