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