协程并发
协程的饥饿问题
长协程会长期占用M, 导致其它饥饿
线程循环(触发切换)
但全局队列可能会存在饥饿问题, 通过再添加一层循环解决.
以一定的概率, 从全局队列拿任务到M中(代码中是每执行61次)
切换时机
- 主动挂起(runtime.gopark)
time.Sleep中会包含gopark操作
- 系统调用完成时
总结
- 如果协程顺序执行,会有饥饿问题
- 协程执行中间,将协程挂起,执行其他协程
- 完成系统调用时挂起,也可以主动挂起
- 防止全局队列饥饿,本地队列随机抽取全局队列 永远不主动挂起, 也不系统调用, 如何处理?
抢占式调度
- 有没有一个地方, 经常会被调用
- 涉及到调用其他方法都会调用 runtime.morestak()
- morestack的本意是检查协程栈知否有足够的空间
- 调用方法时, 会被编译器插入morestack()
标记抢占
- 系统检测到Goroutine运行超过10ms
- 将g.stackguard0置为0xfffffade
抢占
- 执行morestack()时判断是否被抢占
- 如果被抢占, 回到schedule()
- 基于协作的抢占式调度
- 如果任何函数调用(没有morestack), 则使用基于信号的抢占式调度
线程信号
- 操作系统中, 有很多基于信号的底层通信方式 eg. SIGPIPE / SIGURG / SIGHUP
- 线程可以注册对应信号的处理函数
基于信号的抢占式调度
- 注册SIGURG信号的处理函数
- GC工作时, 向目标线程发送信号
- 线程收到信号, 会发生调度
总结
- 基于系统调用和主动挂起,协程可能无法调度
- 基于协作的抢占式调度:业务主动调用morestack()
- 基于信号的抢占式调度:强制线程调用doSigPreempt()