您好,欢迎来到华佗健康网。
搜索
您的当前位置:首页[note/golang]GopherCon 2018 - Golang调度器/GMP笔记

[note/golang]GopherCon 2018 - Golang调度器/GMP笔记

来源:华佗健康网

调度对象——Goroutine

  • Goroutine是用户空间线程 (完全由Go运行时管理,没有OS介入)
  • 轻量,开销小(更小的内存开销,更快的创建、销毁,上下文切换)

参考对比

为什么需要调度器?

由于Goroutine是用户层面的抽象,操作系统只认识内核线程。需要Go 调度器将多个 Goroutine 映射到实际的内核线程上。

对应会需要完成两项工作——Goroutine分发和创建线程。

Go 调度器的设计目标

  • 使用少量的内核线程: 创建内核线程代价高昂,因此调度器力求高效地复用内核线程。
  • 支持高并发: Go 程序会涉及大量的 Goroutine,调度器需要满足这种需求。
  • 利用并行性: 原则上在N核机器上,Go程序应能够并行N个Goroutine。

何时进行调度?

操作可能影响 Goroutine 执行时,Go 调度器就会介入

  • Goroutine 创建
  • Goroutine 阻塞: 如等待channel或mutex
  • 系统调用: 阻塞的系统调用同时会阻塞内核线程

Go 调度器关键思想

  • 运行队列(runqueue): 等待运行的 Goroutine 被放置在队列中。最初,使用的是单个全局队列,但在多线程情况下会导致竞争问题。解决方案是分布式运行队列,每个线程都获取自己的队列。
  • 线程复用: 空闲线程不进行销毁,会被用在检查全局运行队列、网络轮询、尝试运行gc任务、工作窃取上,如果仍然空闲会放置在一个链表中等待复用,以期最大利用并行性。
  • GOMAXPROCS:正在运行Goroutine的线程数为CPU逻辑核心数。
  • 工作窃取(work-stealing): 如果一个线程的本地运行队列为空,它可以随机“窃取”其他线程队列中一半的任务,平衡线程之间的工作负载。
  • 移交(hand-off): 当一个线程被阻塞(例如,由于系统调用)超过一段时间时,它关联的运行队列会由“sysmon”转交到另一个线程,此时唤醒(Unpark)可复用的线程,如果需要的话创建新线程(线程数只适用于正在运行Goroutine的线程),防止 Goroutine 饥饿。
  • 协作式抢占: Go 调度时间节点依赖于程序自发调用调度器。但如果一个 Goroutine 长时间占用 CPU 不放,会由“sysmon”后台线程检测到(>10ms运行,警告)在可能的情况下抢占并将其放置于全局运行队列,确保公平性并防止饥饿。
  • 全局运行队列: 全局运行队列充当被抢占的 Goroutine 的低优先级队列。
  • “sysmon”线程:检测长时间运行的Goroutine,移交运行队列。

Go 调度过程示例

局限性

  • 没有 Goroutine 优先级: 所有 goroutine 都被平等对待,这在某些任务需要更高优先级的情况下可能会出现问题。
  • 没有强抢占: 协作式抢占无法保证公平性或延迟。

(后续有引入,参见https://stackoverflow.com/questions/73915144/why-is-go-considered-partially-preemptive)

  • 对硬件拓扑的感知有限: 调度器目前缺乏对底层硬件拓扑的感知,局部性利用程度不高。

参考资料与扩展阅读

  • https://www..com/watch?v=YHRO5WQGh0k (*) 参考talk
  • https://speakerdeck.com/kavya719/the-scheduler-saga (*) 参考slices
  • https://www..com/watch?v=wQpC99Xu1U4 强抢占的引入
  • https://mukeshpilaniya.github.io/posts/Go-Schedular/

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- huatuo0.com 版权所有 湘ICP备2023021991号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务