用 make(chan T, N) 创建带缓冲 channel 就是最简消息队列:天然并发安全、底层环形队列+goroutine等待队列,sendx/recvx自动管理读写,qcount实时记录长度;N需按场景选取,日志100~1000、任务10~50、通知1~5;无缓冲channel非队列,是同步点。

用 make(chan T, N) 创建带缓冲 channel 就是最简消息队列
不需要额外依赖、不写锁、不造轮子——Go 原生 channel 加上缓冲区,就是最轻量、最安全的内存级消息队列。它天然并发安全,底层是环形队列 + goroutine 等待队列,sendx 和 recvx 指针自动管理读写位置,qcount 实时记录长度,比手写锁保护的 slice 队列更可靠。
- 缓冲大小
N不是“越多越好”:设太大(如 10000)可能掩盖背压问题,导致 OOM;太小(如 1)又容易让生产者频繁阻塞 - 典型取值参考:日志采集可设 100~1000,任务调度建议 10~50,纯通知类消息 1~5 即可
- 切忌用无缓冲
make(chan T)当队列——它本质是同步点,不是队列,一发一收才继续,无法缓存
必须显式 close() 并用 val, ok := 判断关闭状态
很多初学者在消费者侧直接写 for msg := range ch,看似简洁,但一旦生产者 panic 或提前退出没关 channel,消费者就永久阻塞在 range 上,整个 goroutine 泄漏。真实场景中,生产者生命周期常不可控(比如 HTTP handler 中启动),必须主动管理关闭时机。
- 关闭前确保所有发送已完成,否则会 panic:
panic: send on closed channel - 接收端永远用
msg, ok := ,ok == false表示 channel 已关闭且无剩余数据 - 不要在多个 goroutine 中重复调用
close(ch),会 panic:panic: close of closed channel
go func() {
defer close(taskQueue) // 在 defer 中 close 更安全
for i := 0; i < 10; i++ {
taskQueue <- Task{ID: i}
time.Sleep(100 * time.Millisecond)
}
}()
for {
task, ok := <-taskQueue
if !ok {
break // 退出循环
}
process(task)
}
用 select + default 实现非阻塞发送,避免生产者卡死
当队列满时,ch 会阻塞,如果生产者是关键路径(比如 HTTP 请求处理),卡住等于服务不可用。这时候不能靠增大缓冲区硬扛,而要用 select 做快速失败或降级。
-
select是 Go 的多路复用原语,配合default可实现“尝试发送,失败就走其他逻辑” - 常见降级策略:打日志告警、丢弃低优先级消息、写入本地磁盘暂存、返回客户端限流响应
- 注意:不能在
default分支里加time.Sleep循环重试,那会退化成忙等,CPU 拉满
select {
case taskQueue <- task:
// 发送成功
default:
log.Warn("task queue full, dropping task")
// 或:go diskBuffer.Write(task) —— 异步落盘
}并发消费时,别用 range 启动无限 worker,要配 sync.WaitGroup
网上常见错误模式:for i := 0; i 。这看似启动了 4 个消费者,但实际所有 goroutine 共享同一个 ch 变量,且没有机制等待它们结束——主 goroutine 一退出,整个程序就终止,正在处理的任务直接被杀掉。
立即学习“go语言免费学习笔记(深入)”;
- 必须用
sync.WaitGroup记录活跃 worker 数,并在每个 worker 结束时wg.Done() - 关闭 channel 后,worker 自然退出
for range,此时再wg.Wait()才算真正完成 - 若需支持动态增减 worker,就得换用更复杂的信号量或上下文控制,基础场景不推荐
var wg sync.WaitGroup
for i := 0; i < 4; i++ {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
for task := range taskQueue {
processTask(task, workerID)
}
}(i)
}
// 生产完成后关闭
close(taskQueue)
wg.Wait() // 等所有 worker 处理完才继续
真正难的不是写出来,而是想清楚:这条消息丢了能不能接受?延迟 200ms 是否在 SLA 内?队列满时该拒绝还是排队?这些决策比语法更重要——channel 是工具,业务语义才是核心。










