golang调度器

杜子龙 · · 220 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

程序启动时 创建n个操作系统线程 每个线程上面都会运行调度函数;

由于 Go 语言是协作式的调度,不会像线程那样,在时间片用完后,由 CPU 中断任务强行将其调度走。对于 Go 语言中运行时间过长的 goroutine,Go scheduler 有一个后台线程在持续监控,一旦发现 goroutine 运行超过 10 ms,会设置 goroutine 的“抢占标志位”,之后调度器会处理。但是设置标志位的时机只有在函数“序言”部分,对于没有函数调用的就没有办法了。
由于 Go 1.14 前都无法抢占正在执行无限循环且没有任何函数调用的 goroutine,因此一旦出现死循环,将要进行 GC 的时候,其他所有 goroutine 都会停止,并且都在等着无限循环的 goroutine 停下来,遗憾的是,由于 for{} 循环里没有进行函数调用,无法插入抢占标记并进行抢占。

因运行时间过长与因系统调用时间过长而导致的抢占是有差别的:
对于运行时间过长的goroutine,系统监控线程首先会提出抢占请求,然后工作线程在适当的时候会去响应这个请求并暂停被抢占goroutine的运行,最后工作线程再调用schedule函数继续去调度其它goroutine;
而对于系统调用执行时间过长的goroutine,调度器并没有暂停其执行,只是剥夺了正在执行系统调用的工作线程所绑定的p,要等到工作线程从系统调用返回之后绑定p失败的情况下该goroutine才会真正被暂停运行。

由于运行时间过长导致的抢占调度,可以看到go的抢占调度机制并非无条件的抢占。需要抢占时,监控线程负责给被抢占的goroutine设置抢占标记,被抢占的goroutine再在函数的的入口处检查g的stackguard0成员决定是否需要调用morestack_noctxt函数,从而最终调用到newstack函数处理抢占请求。

当M0返回时,它会尝试从其他线程中“偷”一个上下文过来,如果没有偷到,会把Goroutine放到Global runqueue中去,然后把自己放入线程缓存中。 上下文会定时检查Global runqueue。

上一讲讲完了 main goroutine 的诞生,它不是第一个,算上 g0,它要算第二个了。不过,我们要考虑的就是这个 goroutine,它会真正执行用户代码。
g0 栈用于执行调度器的代码,执行完之后,要跳转到执行用户代码的地方,如何跳转?这中间涉及到栈和寄存器的切换。

开始main goroutine 1首先创建创建监控线程,该线程独立于调度器,不需要跟 p 关联即可运行 2创建监控线程,该线程独立于调度器,不需要跟 p 关联即可运行 3开启垃圾回收器 4main 包的初始化,递归的调用我们 import 进来的包的初始化函数 5调用 main.main 函数 6进入系统调用,退出进程,可以看出 main goroutine 并未返回,而是直接进入系统调用退出进程了exit(0)


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:杜子龙

查看原文:golang调度器

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:701969077

220 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传