2. Goroutine 基础:创建、生命周期与线程对比
Goroutine 是 Go 语言并发编程的核心抽象,也是 Go 调度器直接管理的执行单元。理解 Goroutine 的创建方式、生命周期状态以及它与操作系统线程的本质差异,是掌握调度器工作原理的前提。
2.1 Goroutine 创建
在 Go 中,创建一个 Goroutine 只需在函数调用前加上关键字 go。Go 运行时会将这个函数调用包装为一个 Goroutine,并放入调度器的待运行队列。
package main
import (
"fmt"
"time"
)
func say(msg string) {
for i := 0; i < 3; i++ {
fmt.Println(msg)
time.Sleep(100 * time.Millisecond)
}
}
func main() {
go say("hello") // 创建 Goroutine
go say("world") // 创建另一个 Goroutine
time.Sleep(1 * time.Second) // 等待 Goroutine 执行
}
创建 Goroutine 时,Go 运行时会分配一个初始栈(约 2KB),并记录其调度元信息。与线程不同,Goroutine 的创建成本极低,一个进程可以轻松创建数十万甚至上百万个 Goroutine。
| 创建方式 | 示例 | 说明 |
|---|---|---|
| 直接调用函数 | go myFunc() |
创建 Goroutine 执行指定函数 |
| 匿名函数 | go func() { ... }() |
创建 Goroutine 执行闭包逻辑 |
| 方法调用 | go obj.Method() |
创建 Goroutine 执行对象方法 |
提示:Goroutine 的初始栈仅 2KB,且可动态增长(最大可达 1GB),这使得创建大量 Goroutine 时内存开销远小于线程。
2.2 Goroutine 生命周期
Goroutine 的生命周期包含三个核心阶段:创建、运行、退出。在运行阶段,Goroutine 会在调度器的管理下经历多种状态转换。
Goroutine 的主要状态包括:
- Grunnable:Goroutine 已创建或从等待中恢复,位于调度器的运行队列中,等待被 M(线程)获取执行。
- Grunning:Goroutine 正在某个 M 上执行用户代码。
- Gwaiting:Goroutine 因等待 channel 操作、锁、定时器或 I/O 而阻塞。
- Gsyscall:Goroutine 正在执行系统调用,此时 M 与 P 解绑,等待系统调用返回。
- Gdead:Goroutine 执行完毕,结构体被放入空闲列表以备复用。
注意:Goroutine 的退出是不可控的。主 Goroutine 退出时,所有其他 Goroutine 会立即被终止。因此,必须使用
sync.WaitGroup 或 channel 来确保所有 Goroutine 完成。
2.3 Goroutine 与线程对比
Goroutine 常被称为“轻量级线程”,但它们在创建成本、调度方式、栈管理等方面存在本质差异。下表从多个维度进行对比:
| 对比维度 | Goroutine | 操作系统线程 |
|---|---|---|
| 创建成本 | 约 2KB 初始栈,创建时间约 0.1μs | 默认 1MB 栈,创建时间约 1μs |
| 栈管理 | 动态增长/收缩,初始 2KB,最大 1GB | 固定大小,通常 1MB-8MB |
| 调度方式 | 用户态协作式调度(Go 运行时) | 内核态抢占式调度(OS 内核) |
| 上下文切换 | 约 0.2μs,仅保存少量寄存器 | 约 1-10μs,涉及内核态切换 |
| 创建数量 | 轻松创建数十万至百万 | 通常数千至数万 |
| 通信方式 | Channel(推荐)、共享内存 | 共享内存、信号量、管道 |
| 标识 | 无全局 ID,通过 channel 或 context 传递 | 有全局线程 ID(TID) |
2.4 Goroutine 的核心优势
基于上述对比,Goroutine 在以下场景中具有显著优势:
- 高并发 I/O:处理数千个网络连接时,每个连接使用一个 Goroutine 比使用线程节省大量内存。
- 微服务架构:每个请求可以启动一个 Goroutine 处理,创建和销毁成本极低。
- 定时任务:结合
time.Ticker或time.After,Goroutine 可以高效管理大量定时器。 - 流水线处理:通过 channel 连接多个 Goroutine,形成高效的数据处理流水线。
核心要点:Goroutine 的轻量特性(小栈、用户态调度、动态增长)使其成为 Go 并发编程的基石。理解 Goroutine 的生命周期状态转换,是后续学习调度器 M/P/G 模型的前提。