无缓冲 channel 易致 goroutine 频繁阻塞,应改用带缓冲 channel;缓冲区大小需权衡内存与延迟,典型值如 make(chan *LogEntry, 1024)。

channel 容量设置不当导致频繁阻塞
无缓冲 channel(make(chan int))在每次发送和接收时都需双方 goroutine 同时就绪,实际压测中容易成为性能瓶颈。尤其在生产者快、消费者慢的场景下,send 会直接挂起 goroutine,触发调度开销。
改用带缓冲 channel 可解耦收发节奏,但缓冲区不是越大越好——过大会增加内存占用,还可能掩盖消费延迟问题。
- 典型合理值:根据业务单次批处理量设定,例如日志采集常用
make(chan *LogEntry, 1024) - 避免
make(chan int, 1 这类“保险式”大缓冲,它会让 channel 占用数 MB 内存且延迟不可控 - 若无法预估流量,可用
select配合default实现非阻塞写入,再降级到本地队列或丢弃
频繁创建/关闭 channel 引发 GC 压力
在循环内反复调用 make(chan ...) 或对已关闭 channel 执行 close(),会快速生成大量短期对象,加剧垃圾回收频率。pprof 中常表现为 runtime.makeslice 和 runtime.chansend 占比异常高。
关键原则:channel 是长生命周期通信载体,不是一次性的消息容器。
立即学习“go语言免费学习笔记(深入)”;
- 将 channel 作为结构体字段或包级变量复用,而非函数局部变量
- 禁止在 for 循环里
ch := make(chan int); go worker(ch)—— 应提前创建好 channel 并复用 - 关闭 channel 仅应在明确“所有发送者已退出”时由最后一个发送者调用;接收端永远不要 close
range 遍历未关闭 channel 导致 goroutine 泄漏
for v := range ch 会一直阻塞等待新值,如果 sender 忘记 close 或 panic 退出,该 goroutine 就永久挂起,形成泄漏。pprof 查看 goroutine 数量持续上涨即可定位。
必须确保 range 的 channel 有确定的关闭时机,且关闭行为可被 receiver 感知。
- sender 完成后显式调用
close(ch),receiver 才能安全退出 - 若 sender 有多个,用
sync.WaitGroup等待全部完成再 close,不能靠计数器——竞态风险高 - 紧急退出场景可用
context.Context+select替代纯range,例如:for { select { case v, ok := <-ch: if !ok { return } process(v) case <-ctx.Done(): return } }
用 select 处理多 channel 时忽略 default 分支
当 select 中所有 channel 都不可读/写时,若无 default,goroutine 会阻塞,这在需要响应超时、心跳或控制信号的场景下极危险。
更隐蔽的问题是:即使写了 default,若其中逻辑耗时过长(如打印日志、调用 HTTP),也会拖慢主循环吞吐。
- default 分支应尽量轻量,只做标记或投递事件,不执行 IO 或复杂计算
- 避免在 select 中混用同步操作(如直接调用
time.Sleep),改用time.Afterchannel - 高吞吐服务中慎用
select嵌套,易引发调度延迟;可考虑用chan struct{}统一通知事件











