Go 语言的并发模型是其最亮眼的设计之一。goroutine 的轻量调度、channel 的通信语义、select 的多路复用,这三个核心概念构成了一套优雅而强大的并发编程范式。
goroutine:轻量级线程
goroutine 是 Go 运行时管理的轻量级线程,初始栈大小仅 2KB,远小于操作系统线程的默认栈(通常 1MB)。Go 运行时采用 M:N 调度模型,将多个 goroutine 映射到少量操作系统线程上执行。
goroutine 的最佳实践
- 不要在主函数中直接使用 goroutine 而不等待——主 goroutine 退出会导致整个程序退出。
- 使用 sync.WaitGroup 或 channel 来等待 goroutine 完成。
- 避免 goroutine 泄漏——确保每个 goroutine 都有退出路径。
channel:通信而非共享内存
Go 的并发哲学是"不要通过共享内存来通信,而要通过通信来共享内存"。channel 是 goroutine 之间传递数据的管道,它天然地提供了同步机制。
// 生产者-消费者模式
func producer(ch chan<- int) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}
func consumer(ch <-chan int) {
for v := range ch {
fmt.Println(v)
}
}
有缓冲 vs 无缓冲
- 无缓冲 channel:发送操作会阻塞直到有接收者准备好。适合需要严格同步的场景。
- 有缓冲 channel:缓冲未满时发送不阻塞。适合生产者-消费者速率不匹配的场景,可以削峰填谷。
select:多路复用
select 语句让一个 goroutine 可以同时等待多个 channel 操作。它是 Go 并发编程中最强大的控制结构之一。
select {
case msg := <-ch1:
// 处理 ch1 的消息
case ch2 <- data:
// 向 ch2 发送数据
case <-time.After(5 * time.Second):
// 超时处理
case <-ctx.Done():
// 上下文取消
}
常见并发模式
- 扇出(Fan-out):多个 goroutine 从同一个 channel 读取,并行处理任务。
- 扇入(Fan-in):多个 goroutine 的结果汇聚到一个 channel。
- Pipeline:将处理流程拆分为多个阶段,每个阶段通过 channel 连接。
- Or-Done:监听多个 channel,任意一个返回结果即继续执行。
Go 的并发模型让编写并发程序变得自然——goroutine 负责执行,channel 负责通信,select 负责协调。三者结合,威力无穷。