# 6.12 非均匀访存下的调度模型
## 6.12.1 调度器架构的演变
Go 的运行时调度器只经历了两个主要版本的迭代。Go 语言诞生之初的调度器,和我们现在所看到的从 Go 1.1 起引入的工作窃取调度器。
### 单线程版调度器
最早期(Go 1 之前)的 Go 调度器甚至不能良好的支持多线程 \[Cox, 2008\],即默认的最大 M 数为 1。 这个版本的调度器负责将准备运行的 G 与等待工作的调度程序 M 相匹配。如果有准备好的 G 且没有等待的 M,则会在新的 OS 线程中启动一个新的 m, 这样所有准备好的(有限多个)G 可以同时运行。并且,这时的 M 无法退出。
原因在于最早先的 Go 只适当的支持了 Linux,甚至连目标支持的 OS X (当时还没有更名为 macOS)也尚未实现。 其中一个主要的问题就在调度器锁的处理并不完善、垃圾回收的支持也不够完整。
### 多线程版调度器
随后的一年时间中,调度器得到了完善的改进,能够正式的支持多个系统线程的版本 \[Cox, 2009\]。 但这时仍然需要用户态代码通过 $GOMAXPROCS 或 runtime.GOMAXPROCS() 调用来调整最大核数。 而`m`不能退出的问题仍然没有得到改进。
### 工作窃取调度器
随着 Go 1.1 的出现,Go 的运行时调度器得到了质的飞越,调度器正式引入 M 的本地资源 P \[Vyukov, 2013a\], 大幅降低了任务调度时对全局锁的竞争,提出了沿用至今的 MPG 工作窃取式调度器设计。 我们已经在前面的使用了大量篇幅介绍这一调度器的设计,这里便不再赘述了。
## 6.12.2 改进展望:非均匀访存感知的调度器
目前的调度器设计总是假设 M 到 P 的访问速度是一样的,即不同的 CPU 核心访问多级缓存、内存的速度一致。 但真实情况是,在 NUMA(non-uniform memory access,非均匀访存)架构下,CPU 仅在 局部访问自身 NUMA 节点内的内存时才能获得一致的访问速度。更一般地说,这种基于 NUMA 架构的处理器是也是一个分布式的系统。
![](https://golang.design/under-the-hood/assets/sched-numa.png)**图 1:NUMA 架构**
针对这一点,Go 官方已经提出了具体的调度器设计 \[Vyukov, 2014\],但由于工作量巨大,甚至没有提上日程。
TODO: 讨论设计的优劣
## 小结
Go 语言用户态代码的调度核心在未来的十年里只进行了两次改进,足见其设计功力, 但随着 Go 语言的大规模应用,以及越来越多的在多核机器上使用调度器的性能问题也会逐渐暴露出来, 让我们对未来下一个大版本的改进拭目以待。
- 第一部分 :基础篇
- 第1章 Go语言的前世今生
- 1.2 Go语言综述
- 1.3 顺序进程通讯
- 1.4 Plan9汇编语言
- 第2章 程序生命周期
- 2.1 从go命令谈起
- 2.2 Go程序编译流程
- 2.3 Go 程序启动引导
- 2.4 主Goroutine的生与死
- 第3 章 语言核心
- 3.1 数组.切片与字符串
- 3.2 散列表
- 3.3 函数调用
- 3.4 延迟语句
- 3.5 恐慌与恢复内建函数
- 3.6 通信原语
- 3.7 接口
- 3.8 运行时类型系统
- 3.9 类型别名
- 3.10 进一步阅读的参考文献
- 第4章 错误
- 4.1 问题的演化
- 4.2 错误值检查
- 4.3 错误格式与上下文
- 4.4 错误语义
- 4.5 错误处理的未来
- 4.6 进一步阅读的参考文献
- 第5章 同步模式
- 5.1 共享内存式同步模式
- 5.2 互斥锁
- 5.3 原子操作
- 5.4 条件变量
- 5.5 同步组
- 5.6 缓存池
- 5.7 并发安全散列表
- 5.8 上下文
- 5.9 内存一致模型
- 5.10 进一步阅读的文献参考
- 第二部分 运行时篇
- 第6章 并发调度
- 6.1 随机调度的基本概念
- 6.2 工作窃取式调度
- 6.3 MPG模型与并发调度单
- 6.4 调度循环
- 6.5 线程管理
- 6.6 信号处理机制
- 6.7 执行栈管理
- 6.8 协作与抢占
- 6.9 系统监控
- 6.10 网络轮询器
- 6.11 计时器
- 6.12 非均匀访存下的调度模型
- 6.13 进一步阅读的参考文献
- 第7章 内存分配
- 7.1 设计原则
- 7.2 组件
- 7.3 初始化
- 7.4 大对象分配
- 7.5 小对象分配
- 7.6 微对象分配
- 7.7 页分配器
- 7.8 内存统计
- 第8章 垃圾回收
- 8.1 垃圾回收的基本想法
- 8.2 写屏幕技术
- 8.3 调步模型与强弱触发边界
- 8.4 扫描标记与标记辅助
- 8.5 免清扫式位图技术
- 8.6 前进保障与终止检测
- 8.7 安全点分析
- 8.8 分代假设与代际回收
- 8.9 请求假设与实务制导回收
- 8.10 终结器
- 8.11 过去,现在与未来
- 8.12 垃圾回收统一理论
- 8.13 进一步阅读的参考文献
- 第三部分 工具链篇
- 第9章 代码分析
- 9.1 死锁检测
- 9.2 竞争检测
- 9.3 性能追踪
- 9.4 代码测试
- 9.5 基准测试
- 9.6 运行时统计量
- 9.7 语言服务协议
- 第10章 依赖管理
- 10.1 依赖管理的难点
- 10.2 语义化版本管理
- 10.3 最小版本选择算法
- 10.4 Vgo 与dep之争
- 第12章 泛型
- 12.1 泛型设计的演进
- 12.2 基于合约的泛型
- 12.3 类型检查技术
- 12.4 泛型的未来
- 12.5 进一步阅读的的参考文献
- 第13章 编译技术
- 13.1 词法与文法
- 13.2 中间表示
- 13.3 优化器
- 13.4 指针检查器
- 13.5 逃逸分析
- 13.6 自举
- 13.7 链接器
- 13.8 汇编器
- 13.9 调用规约
- 13.10 cgo与系统调用
- 结束语: Go去向何方?