Go 语言的并发模型基于 CSP 理论,核心理念是"不要通过共享内存来通信, 而应通过通信来共享内存"。本文将深入 Goroutine 的调度原理和 Channel 的实际应用。
Goroutine:轻量级并发单元
一个 Goroutine 的初始栈大小只有约 2KB,且可以动态增长和收缩。 在同一个进程中同时运行数万个 Goroutine 完全没有压力。
func main() {
for i := 0; i < 10000; i++ {
go worker(i)
}
time.Sleep(time.Second)
}
GPM 调度模型
- G(Goroutine):用户态的轻量级协程
- P(Processor):逻辑处理器,数量由
GOMAXPROCS决定 - M(Machine):操作系统线程,实际执行计算的单元
Channel:Goroutine 之间的通信桥梁
ch := make(chan int) // 无缓冲
ch := make(chan int, 10) // 有缓冲,容量 10
ch <- 42 // 发送
value := <-ch // 接收
// 单向 Channel
func producer(out chan<- int) { ... }
func consumer(in <-chan int) { ... }
并发模式实战
Pipeline 模式
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums { out <- n }
close(out)
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in { out <- n * n }
close(out)
}()
return out
}
func main() {
for result := range square(square(gen(1, 2, 3, 4, 5))) {
fmt.Println(result) // 1, 16, 81, 256, 625
}
}
Fan-in 模式
func fanIn(channels ...<-chan int) <-chan int {
var wg sync.WaitGroup
out := make(chan int)
for _, c := range channels {
wg.Add(1)
go func(c <-chan int) {
defer wg.Done()
for v := range c { out <- v }
}(c)
}
go func() { wg.Wait(); close(out) }()
return out
}
Context:优雅的取消与超时
func worker(ctx context.Context, id int) error {
select {
case <-time.After(time.Duration(id) * time.Second):
return nil
case <-ctx.Done():
return ctx.Err()
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
// ... 启动 workers
}
常见错误与最佳实践
- 不要通过共享内存来通信。优先使用 Channel 传递数据。
- 谁创建 Channel 谁关闭。向已关闭的 Channel 发送数据会 panic。
- 始终传递 Context。它是超时和取消控制的标准方式。
- 使用
sync.WaitGroup等待 Goroutine 完成。
总结
Go 的并发模型优雅而强大。记住 Go 的并发哲学:通过通信来共享内存,而非通过共享内存来通信。
← 返回首页