在大多数情况下 , 我们都会使用 Go 的默认设置 , 也就是活跃线程数等于 CPU 个数 , 在这种情况下不会触发操作系统的线程调度和上下文切换 , 所有的调度都会发生在用户态 , 由 Go 语言调度器触发 , 能够减少非常多的额外开销 。
操作系统线程在 Go 语言中会使用私有结构体 runtime.m 来表示
type m struct {g0*gcurg *g...}其中 g0 是持有调度栈的 goroutine , curg 是在当前线程上运行的用户 goroutine , 用户 goroutine 执行完后 , 线程切换回 g0 上 , g0 会从线程 M 绑定的 P 上的等待队列中获取 goroutine 交给线程 。
P调度器中的处理器 P 是线程和 goroutine 的中间层 , 它能提供线程需要的上下文环境 , 也会负责调度线程上的等待队列 , 通过处理器 P 的调度 , 每一个内核线程都能够执行多个 goroutine , 它能在 goroutine 进行一些 I/O 操作时及时切换 , 提高线程的利用率 。因为调度器在启动时就会创建 GOMAXPROCS 个处理器 , 所以 Go 语言程序的处理器数量一定会等于 GOMAXPROCS , 这些处理器会绑定到不同的内核线程上并利用线程的计算资源运行 goroutine。
此外在调度器里还有一个全局等待队列 , 当所有P本地的等待队列被占满后 , 新创建的 goroutine 会进入全局等待队列 。P 的本地队列为空后 , M 也会从全局队列中拿一批待执行的 goroutine 放到 P 本地的等待队列中 。
GMP模型图示

文章插图
GMP模型图示
- 全局队列:存放等待运行的G 。
- P的本地队列:同全局队列类似 , 存放的也是等待运行的G , 存的数量有限 , 不超过256个 。新建G时 , G优先加入到P的本地队列 , 如果队列已满 , 则会把本地队列中一半的G移动到全局队列 。
- P列表:所有的P都在程序启动时创建 , 并保存在数组中 , 最多有GOMAXPROCS(可配置)个 。
- M:线程想运行任务就得获取P , 从P的本地队列获取G , P队列为空时 , M也会尝试从全局队列拿一批G放到P的本地队列 , 或从其他P的本地队列偷一半放到自己P的本地队列 。M运行G , G执行之后 , M会从P获取下一个G , 不断重复下去 。
- goroutine 调度器和OS调度器是通过M结合起来的 , 每个M都代表了1个内核线程 , OS调度器负责把内核线程分配到CPU上执行 。
- work stealing机制 , 当本线程无可运行的G时 , 尝试从其他线程绑定的P偷取G , 而不是销毁线程 。
- hand off机制 , 当本线程因为G进行系统调用阻塞时 , 线程释放绑定的P , 把P转移给其他空闲的线程执行 。
协作式调度依靠被调度方主动弃权 , 系统监控到一个 goroutine 运行超过10ms会通过 runtime.Gosched 调用主动让出执行机会 。抢占式调度则依靠调度器强制将被调度方被动中断 。
推荐阅读
- Win10系统下搭建Go lang开发环境更换国内源并且体验宇宙最快框架
- 电脑运行慢?并不是它的性能不行,关掉一个开关,让你电脑飞起来
- Nginx在高并发下的性能优化点!有这篇就够了
- 网传普洱茶减肥不靠谱 并不能分解脂肪
- Linux服务端最大并发数是多少?
- 服务器上网友上传重复图片太多,几步操作检测重复图片并删除
- Win10电脑搜狗输入法无法输入中文并且输入法栏不见了的解决方法
- 81个用于日常问题的Python代码片段
- 架构设计 | 高并发流量削峰,共享资源加锁机制
- 用Python实现十大经典排序算法-插入、选择、快速、冒泡、归并等
