Go 中可用带缓冲 channel 实现阻塞队列:make(chan Task, 100) 创建容量为100的队列,满则生产者阻塞、空则消费者阻塞,天然线程安全,无需额外锁或 sync.Cond。

Go 里没有标准库提供的“阻塞队列”类型,但用 chan + sync.Cond 或带缓冲的 chan 配合 select 就能实现等效行为——关键不是造轮子,而是选对原语、避开死锁和 goroutine 泄漏。
用带缓冲 channel 实现轻量级阻塞写入
无缓冲 chan 是同步点,一发一收;带缓冲的才是“队列”。它天然支持生产者阻塞(缓冲满)、消费者阻塞(缓冲空),无需额外锁或条件变量。
- 缓冲大小必须显式指定:
make(chan Task, 100),零值chan会 panic - 写入时若缓冲已满,
ch 会永久阻塞,直到有空间——这正是“阻塞队列”的核心语义 - 读取时若缓冲为空,
同样阻塞,直到有数据——适合 worker 持续拉取 - 别用
len(ch)判断是否可写/可读:它只反映当前缓冲长度,不反映 goroutine 等待状态,也不保证后续操作不阻塞
用 sync.Cond + sync.Mutex 实现自定义 ring buffer 阻塞队列
当需要精确控制容量、复用内存、或支持 Peek/Size 等扩展操作时,基于循环数组 + sync.Cond 的方案更灵活。但要注意 cond 的等待逻辑必须在锁内完成,且唤醒后要重新检查条件。
-
Enqueue中的for q.count == q.size { q.cond.Wait() }不是 if,避免虚假唤醒导致写入越界 -
Dequeue同理,必须用 for 循环等待非空,不能只 check 一次 -
q.cond.Signal()调用后不保证立即唤醒,只是通知“可能有变化”,接收方仍需重新校验状态 - 不要在持有锁期间做耗时操作(如网络调用、复杂计算),否则所有等待 goroutine 都被卡住
避免死锁:关闭 channel 和 range 的配合时机
channel 关闭后,range 会自动退出,但关闭时机错位极易引发 panic 或死锁。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 只能由**唯一生产者**或**协调 goroutine** 关闭 channel,多个 goroutine 同时 close 会 panic
- 关闭前确保所有生产者已退出,否则可能丢数据或触发 “send on closed channel” panic
- 消费者用
for val := range ch是安全的,但若混用和 <code>range,容易漏掉最后一批数据 - 如果消费者需区分“空闲等待”和“队列关闭”,改用
val, ok := ,<code>ok == false表示 channel 已关
goroutine 泄漏比性能差更危险
阻塞队列场景下最隐蔽的问题不是慢,而是 goroutine 卡住不退出,持续占用栈和调度资源。
- 永远不要启动一个 goroutine 去
range一个可能永不关闭的 channel —— 它会永远挂起 - worker pool 中,用
sync.WaitGroup等待所有 worker 退出,再关闭结果 channel;反过来(先关 channel 再等 wg)会导致部分 worker 在时 panic - 超时必须设在调用方:用
context.WithTimeout包裹入队逻辑,而不是在 channel 内部实现“超时丢弃” - 监控
runtime.NumGoroutine(),突增且不回落就是泄漏信号
真正难的不是让队列“阻塞”,而是让阻塞有边界、有退路、有可观测性——channel 的阻塞是协作式的,不是故障;而没配好 close、WaitGroup 或超时,才让它变成黑洞。

















