1. Goroutine 的基础
1.1 定义
- Goroutine 是由 Go 运行时管理的轻量级线程。
- 启动一个 goroutine 只需要极小的内存(几 KB),而不像系统线程那样需要几 MB。
1.2 启动
-
go func()
启动一个新的 goroutine,函数体在新的 goroutine 中并发执行。 -
例如:
go func() { fmt.Println("Hello from goroutine") }()
2. Go Scheduler (调度器)
2.1 调度器角色
- Go 运行时包含一个调度器,负责管理和调度所有的 goroutine。
- 调度器确保所有的 goroutine 能够公平地获取执行时间。
2.2 M:N 模型
- Go 采用 M:N 模型,将 M 个 goroutine 映射到 N 个系统线程上。
- 调度器使用工作窃取算法,使得 goroutine 能够在多个线程之间平衡负载。
3. Goroutine 的执行流程
3.1 创建
- 使用
go
关键字启动 goroutine 时,运行时系统会创建一个新的G
(表示 goroutine)对象,并将其添加到运行队列。
3.2 调度
- 调度器根据系统资源和 goroutine 的优先级,将
G
对象分配给P
(Processor),P
代表逻辑处理器。 P
再将G
分配给M
(Machine),M
代表实际的操作系统线程。
3.3 执行
M
执行G
中的代码,当G
阻塞或完成时,M
将其从P
的运行队列中移除,并寻找下一个G
执行。
4. 并发与同步
4.1 并发
- 多个 goroutine 可以同时运行,并利用多核处理器提升性能。
- 例如,在一个 HTTP 服务器中,每个请求可以启动一个新的 goroutine 来处理,从而实现高并发处理。
4.2 同步
- 当多个 goroutine 需要访问共享资源时,需要同步机制来避免竞态条件。
- Go 提供了多种同步原语,如
sync.Mutex
、sync.WaitGroup
和channels
。
5. Channels (通道)
5.1 定义
- 通道是 Go 语言提供的一种用于 goroutine 之间通信的机制。
- 通道允许安全地在多个 goroutine 之间传递数据。
5.2 使用
-
使用
make
函数创建通道:ch := make(chan int)
-
向通道发送数据和从通道接收数据:
go func() { ch <- 42 // 发送数据到通道 }() value := <-ch // 从通道接收数据 fmt.Println(value)
6. Goroutine 的生命周期管理
6.1 启动
- 启动 goroutine 非常简单,使用
go
关键字。
6.2 运行
- 调度器负责管理和调度 goroutine 的执行。
- Goroutine 可以随时被调度器挂起和恢复。
6.3 终止
- 当 goroutine 的函数体执行完毕后,goroutine 自动终止。
- 必须小心管理 goroutine 的生命周期,避免 goroutine 泄露(即 goroutine 持续运行但无法终止)。
7. 实践中的注意事项
7.1 锁与同步
- 在需要共享数据时,使用
sync.Mutex
保护临界区。 - 使用
sync.WaitGroup
等待一组 goroutine 完成。
7.2 防止泄露
- 确保每个 goroutine 都有退出条件,避免无限循环或阻塞。