跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

如何在Golang中实现简单消息队列_Golang channel与goroutine应用实践

用 make(chan T, N) 创建带缓冲 channel 就是最简消息队列:天然并发安全、底层环形队列+goroutine等待队列,sendx/recvx自动管理读写,qcount实时记录长度;N需按场景选取,日志100~1000、任务10~50、通知1~5;无缓冲channel非队列,是同步点。 用
make(chan T, N)
创建带缓冲 channel 就是最简消息队列 不需要额外依赖、不写锁、不造轮子——Go 原生
channel
加上缓冲区,就是最轻量、最安全的内存级消息队列。它天然并发安全,底层是环形队列 + go routine 等待队列,
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语言参考手册 中文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语言免费学习笔记(深入) ”; 必须用
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 是工具,业务语义才是核心。

相关文章