花拾录
← 返回知识库

Go 的 goroutine 与 channel:并发写法的常见误用

编程语言AI2026/09/220 阅读0 评论

Go 的并发模型以 goroutine 和 channel 为核心,写法简洁,但也容易踩坑。下面梳理几类常见误用,并给出可验证的修正思路。

1. 忘记等待 goroutine 结束

go f() 启动后不会阻塞主流程。如果 main 函数直接返回,未执行完的 goroutine 会被直接终止,输出可能不完整。

修正方式:

  • 用 sync.WaitGroup:wg.Add(1) 放在启动 goroutine 之前,goroutine 内 defer wg.Done(),主流程 wg.Wait()。
  • 用带缓冲的 channel 收集结果,读取预期数量的值后再继续。

注意:wg.Add 写在 goroutine 内部可能与 wg.Wait 产生竞态,是常见错误。

2. 向无缓冲 channel 发送却无人接收

无缓冲 channel 的发送和接收必须同时就绪。若只有一个 goroutine 向无缓冲 channel 发送,而接收方迟迟未启动或已退出,发送方会永久阻塞,进而导致整个程序死锁。

修正方式:

  • 明确接收方一定存在,或改用带缓冲 channel 并设置合理容量。
  • 用 select 配合 default 或 time.After 做超时兜底。

3. 只发送不关闭,range 永不结束

for v := range ch 只有在 channel 被关闭后才会退出。如果发送方忘记 close(ch),循环会一直阻塞等待。

修正方式:

  • 由发送方在发送完成后调用 close(ch);不要在接收方关闭。
  • 向已关闭的 channel 发送会 panic,因此要确保关闭只发生一次。

4. 用 channel 传共享变量,而不是传所有权

Go 的惯例是“通过通信共享内存,而不是通过共享内存通信”。若多个 goroutine 直接读写同一个 map 或 slice,即使有 channel 参与,也可能触发 data race。

修正方式:

  • 用 go run -race 或 go test -race 检测竞态。
  • 要么让数据只在一个 goroutine 内被修改,要么用 sync.Mutex、sync.RWMutex 保护。

5. goroutine 泄漏

启动的 goroutine 因阻塞在 channel 收发、锁等待或网络 IO 上而永远无法退出,就是 goroutine 泄漏。数量持续增长会耗尽内存。

修正方式:

  • 为阻塞操作设置超时或 context.Context 取消信号。
  • 在 select 中监听 ctx.Done(),收到取消后及时返回。
select {
case v := <-ch:
    handle(v)
case <-ctx.Done():
    return
}

6. 误用 nil channel

对 nil channel 的发送和接收都会永久阻塞,但用于 select 时可以动态启用或禁用分支。若不小心把 nil channel 当作普通 channel 使用,程序会卡住。

修正方式:初始化后再使用,或在 select 中利用 nil channel 屏蔽某个分支。

7. 把无缓冲 channel 当同步队列

无缓冲 channel 容量为 0,发送和接收严格配对,吞吐通常低于带缓冲 channel。若生产者速度快于消费者,无缓冲 channel 会频繁阻塞。

修正方式:根据生产/消费速率差设置缓冲大小,或引入批量处理。缓冲大小需要实测,不能凭感觉设定。

小结

  • 启动 goroutine 前先想清楚它何时结束。
  • channel 由发送方关闭,只关闭一次。
  • 用 -race 检测共享内存竞态。
  • 用 context 和超时避免永久阻塞与泄漏。

这些做法都能通过 go vet、go test -race 和实际运行验证,建议在项目中固定为代码审查清单。

评论(0)

  • 还没有评论,来抢沙发~

相关文章