Go中的协程(二) | 青训营笔记

120 阅读2分钟

协程并发

协程的饥饿问题

Pasted image 20230520230310.png 长协程会长期占用M, 导致其它饥饿

线程循环(触发切换)

Pasted image 20230520230549.png

Pasted image 20230520230648.png 但全局队列可能会存在饥饿问题, 通过再添加一层循环解决. 以一定的概率, 从全局队列拿任务到M中(代码中是每执行61次)

Pasted image 20230520230814.png

切换时机

  • 主动挂起(runtime.gopark)

Pasted image 20230520231107.png time.Sleep中会包含gopark操作

  • 系统调用完成时

Pasted image 20230520231320.png

总结

  • 如果协程顺序执行,会有饥饿问题
  • 协程执行中间,将协程挂起,执行其他协程
  • 完成系统调用时挂起,也可以主动挂起
  • 防止全局队列饥饿,本地队列随机抽取全局队列 永远不主动挂起, 也不系统调用, 如何处理?

抢占式调度

  • 有没有一个地方, 经常会被调用
  • 涉及到调用其他方法都会调用 runtime.morestak()
  • morestack的本意是检查协程栈知否有足够的空间
  • 调用方法时, 会被编译器插入morestack()

标记抢占

  • 系统检测到Goroutine运行超过10ms
  • 将g.stackguard0置为0xfffffade

抢占

  • 执行morestack()时判断是否被抢占
  • 如果被抢占, 回到schedule()
  • 基于协作的抢占式调度

Pasted image 20230521141053.png

  • 如果任何函数调用(没有morestack), 则使用基于信号的抢占式调度

线程信号

  • 操作系统中, 有很多基于信号的底层通信方式 eg. SIGPIPE / SIGURG / SIGHUP
  • 线程可以注册对应信号的处理函数

基于信号的抢占式调度

  • 注册SIGURG信号的处理函数
  • GC工作时, 向目标线程发送信号
  • 线程收到信号, 会发生调度

Pasted image 20230521141748.png

总结

  • 基于系统调用和主动挂起,协程可能无法调度
  • 基于协作的抢占式调度:业务主动调用morestack()
  • 基于信号的抢占式调度:强制线程调用doSigPreempt()