在 Go 语言中,进程调度主要是指 Goroutine 的调度。Go 语言的调度器是一个复杂的系统,它负责在不同的 Goroutines 之间进行切换,以实现并发执行。Go 调度器的设计目标是提供高效的并发性,同时保持 CPU 的高利用率和低延迟。
Go 调度器的核心是三个主要的概念:Goroutine(G)、Machine(M)和 Processor(P)。
Goroutine(G):Go语言中并发的最小单位,可以理解为用户态的线程。Goroutines 非常轻量,占用的内存很小,可以同时运行成千上万个 Goroutines。
Machine(M):操作系统线程,M 是内核调度的实体,负责执行 Go 代码。
Processor(P):Go 运行时的逻辑处理器,它作为 M 和 G 之间的中介。每个 P 都有一个本地队列,用于存放待执行的 Goroutines。
调度器的工作流程大致如下:
- 当一个 Goroutine 被创建时,它会被放入一个 P 的本地队列中。
- M 从它绑定的 P 的本地队列中取得一个 Goroutine 来执行。
- 如果 Goroutine 发生阻塞(例如 I/O 操作),M 会将 G 放入全局队列,并释放 P,然后 M 进入休眠状态。
- 当 Goroutine 完成执行后,M 会尝试从全局队列或其它 P 的本地队列中获取新的 Goroutine 来执行。
Go 调度器是非抢占式的,这意味着一旦一个 Goroutine 开始执行,它将一直运行到以下情况之一发生:
- Goroutine 显式地让出执行权(例如通过通道操作或锁的竞争)。
- Goroutine 执行完成。
- 达到抢占点(从 Go 1.13 版本开始,Go 调度器引入了非合作抢占)。
调度器会尝试确保所有 Goroutine 都公平地获得 CPU 时间,并且通过全局队列来平衡负载,防止某些 P 的本地队列被耗尽而其它 P 的本地队列中还有大量待执行的 Goroutines。
此外,Go 调度器还处理系统调用和网络 I/O 的阻塞问题。当 Goroutine 执行系统调用时,如果检测到资源尚未就绪,它会将 Goroutine 挂起,并将其放入网络轮询器的等待队列中,直到资源就绪后再将其放回运行队列。
Go 调度器的设计使得开发者可以轻松地编写并发程序,而无需担心底层的线程管理和调度问题。