Go 的 goroutine
是一种轻量级的线程实现,其底层原理和实现涉及多个方面,包括调度器、栈管理、和运行时系统。以下是对 Go goroutine
底层原理和实现的详细介绍。
1. goroutine
的底层原理
1.1 轻量级线程
-
协作式调度:
goroutine
是 Go 运行时系统提供的一种轻量级线程,底层由 Go 的调度器负责调度。Go 的调度器在用户级别实现了goroutine
的调度,而不是依赖操作系统的线程调度,这使得goroutine
可以更高效地运行。 -
栈管理:
goroutine
使用的是非常小的初始栈(通常只有几 KB),这种设计可以大幅减少内存开销。Go 运行时系统会在需要时自动扩展goroutine
的栈大小,这使得goroutine
可以在保持小内存占用的同时,处理复杂的递归调用或深度操作。
1.2 调度器
-
M:N 模型:Go 使用 M:N 调度模型,其中 M 表示操作系统线程,N 表示
goroutine
。多个goroutine
可以被调度到少量的操作系统线程上,这种设计减少了线程切换的开销,并且更容易在多核 CPU 上进行负载均衡。 -
调度器组件:Go 的调度器包含以下几个主要组件:
P
(Processor):代表逻辑处理器,负责执行goroutine
。调度器根据逻辑处理器的数量来决定有多少个P
。M
(Machine):代表操作系统线程,负责执行P
上的goroutine
。调度器将goroutine
调度到M
上。G
(Goroutine):代表一个goroutine
。每个G
都有自己的栈和调度信息。
-
调度算法:Go 的调度器使用抢占式调度算法。
goroutine
会定期进行时间片轮转,以确保所有goroutine
都能够获得执行时间。调度器会根据goroutine
的状态(如等待、运行、阻塞)来决定如何调度goroutine
。
2. goroutine
的实现细节
2.1 栈管理
-
初始栈:每个
goroutine
从一个小的初始栈开始(通常为 2 KB)。这种小栈使得创建goroutine
的开销很小。 -
栈扩展:当
goroutine
需要更多栈空间时,Go 的运行时系统会自动扩展栈。扩展栈是通过复制当前栈的内容到更大的内存区域来实现的。这种栈扩展的过程是自动进行的,对用户代码是透明的。
2.2 调度器内部结构
-
调度队列:调度器维护多个队列来管理
goroutine
。主要包括:- 运行队列:存放准备执行的
goroutine
。 - 等待队列:存放因等待 I/O 或其他条件而被阻塞的
goroutine
。 - 系统队列:存放等待操作系统线程的
goroutine
。
- 运行队列:存放准备执行的
-
工作窃取:调度器使用工作窃取算法来平衡负载。工作窃取允许一个忙碌的
P
从其他较空闲的P
中窃取goroutine
,以便均衡负载并提高 CPU 使用率。
2.3 线程和协程的切换
-
上下文切换:
goroutine
的上下文切换比操作系统线程的上下文切换要轻量得多,因为它只涉及到用户级别的状态保存和恢复,而不需要操作系统级别的切换。 -
抢占:Go 的调度器会定期检查
goroutine
的状态并进行抢占,以防止某些goroutine
长时间占用 CPU。这种抢占机制保证了所有goroutine
都能公平地获得 CPU 时间。
3. 实现细节和示例
以下是一些 Go 的 goroutine
实现细节的代码示例和解释:
3.1 goroutine
的创建
在 Go 中,通过 go
关键字创建 goroutine
,底层实际调用了 Go 运行时的相关函数:
func newGoroutine(fn func()) *g {
g := new(g)
g.fn = fn
g.stack = makeStack() // 创建初始栈
return g
}
func goStart(g *g) {
// 将 goroutine g 加入调度队列
sched.runQueue.push(g)
}
3.2 栈扩展
当 goroutine
需要更多栈空间时,Go 运行时会自动扩展栈:
func growStack(g *g) {
oldStack := g.stack
newStack := allocateNewStack()
copyStack(oldStack, newStack)
g.stack = newStack
}
3.3 调度器实现
调度器内部维护了多个队列来管理 goroutine
的调度:
type Scheduler struct {
runQueue queue.Queue // 运行队列
waitQueue queue.Queue // 等待队列
mQueue queue.Queue // 系统队列
}
func (s *Scheduler) schedule() {
// 从运行队列中取出一个 goroutine 并执行
g := s.runQueue.pop()
runGoroutine(g)
}
总结
Go 的 goroutine
是一种高效的并发执行单元,底层实现涉及轻量级的线程管理、协作式调度、动态栈管理和高效的调度算法。通过使用这些技术,Go 能够在多核 CPU 上高效地运行大量的 goroutine
,实现简洁且高效的并发编程模型。