带缓冲的 channel(make(chan t, n))是 go 中最轻量、天然 fifo 且并发安全的内存消息队列;n 决定积压能力,需权衡阻塞与内存;应避免无缓冲 channel 做队列、多生产者误关通道、单次读取不 range、select 破坏 fifo 等常见错误。

用 make(chan T, N) 创建带缓冲 channel 就是队列
Go 里最轻量、最直接的内存消息队列,就是带缓冲的 chan。它天然 FIFO、并发安全、零依赖,不是“模拟”队列,它本身就是队列。
- 缓冲大小
N决定积压能力:设太小(比如1)等于没缓冲,生产者一快就阻塞;设太大(比如10000)可能掩盖消费瓶颈,还浪费内存 - 类型选
string简单,但真实项目建议用自定义结构体,比如type Task { ID string; Payload []byte },避免序列化/反序列化出错 - 别用无缓冲 channel(
make(chan T))做队列——它只适合同步通知,一发一收,不满足“暂存”需求
多生产者写入时,close() 必须由单一协程触发
多个 goroutine 往同一个 chan 发消息没问题,但关通道只能关一次,且必须等所有生产者都完成后再关,否则会 panic 或消费者提前退出。
- 常见错误:每个生产者都调
close(ch)→ 运行时报panic: close of closed channel - 正确做法:用
sync.WaitGroup计数生产者完成数,再起一个协程等wg.Wait()后调close(ch) - 消费者必须用
for range ch,不能用单次读取——否则关通道后不会自动退出,会卡死或读零值
select + default 防止生产者/消费者无限阻塞
默认情况下,向满 channel 发送或从空 channel 接收会永久阻塞。在非关键路径或需降级逻辑时,要用 select 控制行为。
- 生产者非阻塞写入示例:
select { case ch <- msg: // 成功 default: // 队列满了,丢弃或打日志或走备用路径 } - 消费者带超时读取:
select { case msg := <-ch: handle(msg) case <-time.After(5 * time.Second): // 超时,可退出或重试 } - 注意:
select里不能同时有多个case ,Go 会随机选一个,破坏 FIFO 语义
不持久、不跨进程、不保序——这些不是 bug,是 channel 队列的设计边界
用 chan 做队列,就默认接受它的限制:进程重启消息全丢;无法被其他进程访问;如果多个消费者并发读,虽然 channel 本身保序,但处理耗时不一会导致“完成顺序 ≠ 入队顺序”。
立即学习“go语言免费学习笔记(深入)”;
- 如果你需要消息不丢,就别指望
chan,该上 Redis 的RPush/BLPop或 RabbitMQ 的 confirm 模式 - 如果你要监控积压量,
len(ch)可读当前已缓存数,但cap(ch)是固定容量,别混淆 - 封装成结构体(如
type MemQueue struct { ch chan Task })不是为了炫技,而是为后续加统计、限流、中间件埋点留接口
channel 队列的价值不在功能多,而在够简单、够快、够可控——一旦你开始给它加重试、死信、持久化,说明它已经不是你的队列了,只是你还没换掉的临时占位符。










