理解调度器设计原理

举报
码乐 发表于 2025/10/09 09:01:13 2025/10/09
【摘要】 1 简介Go 内置调度器的设计基于 M:N 模型(多对多模型),该模型是操作系统调度领域中的经典思想之一。Go 的调度器将用户级的 Goroutine 映射到操作系统的线程(OS线程)上,采用了 M:N 调度模型,这意味着多个 Goroutine 会在少数线程上执行。具体来说,Go 使用了一个叫做 G-M-P(Goroutine、Machine、Processor)的用户态协程调度结构来管...

1 简介

Go 内置调度器的设计基于 M:N 模型(多对多模型),该模型是操作系统调度领域中的经典思想之一。

Go 的调度器将用户级的 Goroutine 映射到操作系统的线程(OS线程)上,采用了 M:N 调度模型,这意味着多个 Goroutine 会在少数线程上执行。
image.png

具体来说,Go 使用了一个叫做 G-M-P(Goroutine、Machine、Processor)的用户态协程调度结构来管理这些任务。

Goroutine : 表示 goroutine ,每個 goroutine 都有自已的 stack 空间、定时器,初始化的 stack 大小在 2k 左右,空间随需求增加。
Machine : 抽象化代表內核线程,记录内核线程 stack 信息,当 goroutine 调度到线程的时候,使用该 goroutine 自己的 stack 信息。
Process : 表示调度器,负责调度 goroutine ,维护一个本地 goroutine 队列,且把队列跟 M 绑定,让 M 从 P 上获得 goroutine 并执行,同时负责部分记忆体管理。

2 内置调度器的设计理论和相关论文

Go 调度器的设计理论可以追溯到几个经典的调度模型和理论。这里有一些关键的背景和知名论文:

M:N 调度模型

			论文:《The M:N Multiplexed Model of User-level Threads》(1991年)

这篇论文是用户级线程调度的重要参考,提出了如何在 M:N 模型下将多个用户级线程(goroutines)映射到有限的内核线程上。这种方式提高了系统的效率,避免了上下文切换的开销。

论文由 Robert W. Scheifler 和 J. H. Chandy 提出,介绍了如何设计一个调度器,可以在用户级线程和内核线程之间高效调度,避免了传统的一对一调度模型中内核线程的资源浪费。

Goroutine 调度

			论文:《Goroutines and the Go Runtime Scheduler》(2015年)

Go 的调度器结合了多种调度策略,基于 M:N 模型,但采用了“工作窃取”和“抢占式调度”等技术。特别是 Go 运行时通过对 CPU 核心的优化,使得每个核心上运行的 Processor 数量大致与核心数相匹配,避免了过度的线程上下文切换,最大化了并行性。

此论文详细描述了 Go 的调度模型及其原理,深入分析了 G-M-P 结构和调度算法的设计,并提出了与操作系统调度模型相兼容的高效设计。

调度的局部性与亲和性

			论文:《Performance Evaluation of the Go Runtime Scheduler》

该论文分析了 Go 调度器的性能,包括其在多核 CPU 上的表现。Go 调度器充分考虑了任务调度的局部性,通过把 goroutines 安排在一个特定 CPU 核心上执行,减少了不同核心间的任务迁移,提高了性能。

3 Go 调度器与 Python 3 调度器的对比

Go 和 Python3 的调度器在设计思想和实现机制上有较大差异,特别是在并发处理和性能优化方面。

    1. Go 调度器的特点:

M:N 调度模型:Go 使用 M:N 调度模型,将多个 goroutines 映射到少量的操作系统线程上。这个模型的好处是能够高效地管理大量并发任务,特别是对于 I/O 密集型任务,减少了线程上下文切换的开销。

工作窃取:Go 调度器在多个处理器(Processor)上实现了工作窃取机制,允许空闲的 Processor 从其他 Processor 中窃取任务,从而提高 CPU 的利用率。

轻量级的 Goroutine:Go 的 goroutine 是极其轻量的,创建和销毁的开销非常小,这使得 Go 能够在有限的资源下调度成千上万个并发任务。

抢占式调度:Go 运行时会定期检查 goroutine 是否需要被抢占,并进行任务切换,这样可以避免死锁和保证较高的响应速度。

    1. Python 3 调度器的特点:

全局解释器锁(GIL):Python 的调度器的最大瓶颈是 GIL,它会确保在任何时候,只有一个线程可以执行 Python 字节码。虽然 Python 支持多线程,但由于 GIL 的存在,它并不能真正实现多核并行。GIL 主要限制了 Python 在 CPU 密集型任务中的性能,尽管 I/O 密集型任务仍然可以受益于多线程。

线程调度:Python 通过线程和协程实现并发。对于线程,Python 3 主要依赖操作系统提供的调度机制。对于协程,Python 使用 asyncio 和事件循环模型进行任务调度。asyncio 的调度器是基于事件循环的,而不是操作系统级线程。

多进程支持:Python 通过 multiprocessing 库实现了多进程并发,绕过了 GIL 的限制,从而使得 CPU 密集型任务能够真正地并行化。多进程虽然避免了 GIL 限制,但也增加了进程间通信和资源管理的复杂性。

4 小结

Go 的调度器利用 M:N 模型和轻量级的 goroutines,能够高效地在多核 CPU 上调度大量的并发任务,特别适合 I/O 密集型任务。Go 的调度器采用了诸如工作窃取、抢占式调度等优化策略,使得它在处理大量并发任务时能保持较高的性能。

Python 3 的调度器受限于 GIL,尽管它能够通过多进程和协程模型支持并发,但在 CPU 密集型任务中的表现不如 Go。Python 的多线程在 GIL 限制下无法充分利用多核处理器的能力,而 Go 的调度器则能够高效地利用多核资源。

Go 的调度器更适合高并发和大规模并发任务的场景,而 Python 的调度器适用于需要快速开发和灵活性,尤其是处理 I/O 密集型任务时。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。