为什么 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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注