goroutine 泄漏主因是启动后缺乏退出机制;应使用 context.Context 或有界 channel 控制生命周期,如 select 配合 ctx.Done() 实现可取消,避免无条件 for range ch。

goroutine 启动后不加约束容易泄漏
启动 goroutine 但没配对的退出机制,是常见内存和 goroutine 泄漏根源。比如 go func() { ... }() 内部死循环或阻塞读 chan,又没提供关闭信号,该 goroutine 就永远卡住。
正确做法是配合 context.Context 或带缓冲/有界 channel 控制生命周期:
- 用
ctx.Done()配合select实现可取消的 goroutine - 避免无条件
for range ch,除非明确知道 sender 会close(ch) - 若 sender 不负责关闭 channel,改用
for { select { case v, ok :=
channel 缓冲区大小不是越大越好
设成 make(chan int, 1000) 看似“防堵”,实则掩盖背压缺失、延迟问题暴露,还可能引发 OOM。缓冲区本质是临时队列,应按实际吞吐节奏和容错需求设定。
典型取值逻辑:
立即学习“go语言免费学习笔记(深入)”;
- 0(无缓冲):适合严格同步协作,如“发完立刻等响应”
- 1:常用于通知类 channel(如
done),或 producer-consumer 中单任务暂存 - N(小正整数):仅当确认 consumer 可能短暂延迟,且 N 能覆盖最坏延迟周期时才设
- 避免用
len(ch)做业务判断——它只是当前未读数,非容量,也不保证并发安全
select default 分支会破坏阻塞等待语义
写 select { case 看似“非阻塞读”,但若本意是“等数据来再干活”,default 会让 goroutine 空转,吃 CPU,还绕过 channel 的天然背压。
真正需要非阻塞场景极少,多数情况应:
- 去掉
default,让 goroutine 挂起等数据,这是 Go 并发模型的设计前提 - 若必须轮询多个 channel 且不能阻塞,用
time.After+select实现超时控制,而非 default - 警惕
select {}写法——它永久挂起,不是空操作,是明确的“永不唤醒”
关闭 channel 的时机和主体必须唯一
向已关闭的 channel 发送数据会 panic:send on closed channel;从已关闭的 channel 接收会立即返回零值+false。但更隐蔽的问题是:谁该关?什么时候关?
规则很简单:
- **只有 sender** 应关闭 channel —— 因为 sender 最清楚“我不会再发了”
- 多个 sender 共享同一 channel?那就不该由任何一方关,改用
sync.WaitGroup或context协调结束 - receiver 绝对不要关 channel,哪怕只读一次也别碰
close() - 关闭前确保所有发送操作已完成(例如用
wg.Wait()等待所有 sender goroutine 结束)
channel 关闭不是“释放资源”的动作,而是语义信号:数据流终结。误关或漏关,都会让协作逻辑断裂,且往往在高并发下才暴露。











