为什么 Go 的并发模型值得专门学
Go 的并发哲学来自 C.A.R. Hoare 的 CSP 理论,与传统线程模型完全不同。理解这套模型,是写出高性能 Go 服务的基础。
Goroutine 到底是什么
goroutine 是由 Go 运行时管理的轻量级线程,创建成本极低(约 2KB 栈空间),可以轻松创建数万个 goroutine 而不耗尽系统资源。与 OS 线程的 1:1 模型不同,Go 使用 M:N 调度——M 个 OS 线程上运行 N 个 goroutine,由 Go 运行时 GOMAXPROCS 控制并行度。
一个简单的 goroutine 示例
// 启动一个 goroutine
go func() {
fmt.Println("running in goroutine")
}()
// 主 goroutine 继续执行,不等待子 goroutine
fmt.Println("main exits")
Channel:goroutine 之间的通信桥梁
channel 是 goroutine 间传递数据的管道,有带缓冲(buffered)和不带缓冲(unbuffered)两种。推荐优先使用无缓冲 channel,这样发送与接收必须配对,强制调用方思考同步问题,避免隐藏的竞态。
ch := make(chan int) // 无缓冲
ch <- 42 // 发送(阻塞直到有人接收)
value := <-ch // 接收
// 带缓冲 channel(队列)
bufCh := make(chan int, 100) // 最多缓存 100 条,不阻塞
select:多路复用的并发协调
select 关键字让一个 goroutine 可以同时监听多个 channel,就跟 select 系统调用监听多个 fd 一样。配合 default case 可以实现非阻塞发送/接收。
select {
case msg := <-ch1:
fmt.Println("received from ch1:", msg)
case msg := <-ch2:
fmt.Println("received from ch2:", msg)
case <-time.After(time.Second):
fmt.Println("timeout")
default:
fmt.Println("no message available")
}
常见踩坑与最佳实践
- 不要用 channel 传递所有数据——共享内存(sync.Mutex/sync.Map)有时更简单
- 永远不要关闭由别人发送的 channel,避免 panic
- 使用 context.Context 来控制 goroutine 的生命周期,而不是用 channel 硬传退出信号
- 在生产环境加上 -race 参数跑单元测试,检查数据竞争
总结
Go 的并发模型用起来简单,但要真正用好,需要理解 goroutine 的调度原理、channel 的阻塞语义,以及 context 在多层调用中的传递方式。掌握这些,你就能写出既高效又安全的并发代码。
来源:dnote.cn
发表回复