go 的 chan 本质是同步通信管道而非队列,无缓冲时需收发配对,有缓冲时缺乏 peek、安全 len、动态扩容等队列能力,len(ch) 仅为瞬时值且无法非阻塞判空。

为什么不能直接用 chan 当队列用
Go 的 chan 本质是同步通信管道,不是缓冲区容器。声明 make(chan int, 0) 是无缓冲通道,每次 send 都要等对面 recv,根本没法“存任务”;即使加了缓冲 make(chan Task, 100),它也不支持 Peek、Len 安全读取、动态扩容,更无法知道当前积压多少任务——你看到的 len(ch) 只是瞬时值,且无法在不阻塞的情况下判断是否为空。
用 chan + sync.WaitGroup 做基础任务分发
真正实用的并发队列,核心是「生产者往 channel 写,消费者从 channel 读,用 sync.WaitGroup 控制生命周期」。这不是模拟队列,而是利用 channel 天然的 FIFO 和背压能力做任务流控。
- 生产者协程调用
ch ,若 channel 满(有缓冲)则阻塞,天然限流 - 每个消费者启动一个
for range ch循环,自动处理所有任务直到close(ch) - 用
sync.WaitGroup等待所有消费者退出,再关闭 channel,避免 panic
示例关键片段:
tasks := make(chan Task, 100)
var wg sync.WaitGroup
<p>// 启动 4 个 worker
for i := 0; i < 4; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for task := range tasks {
process(task)
}
}()
}</p><p>// 发送任务
for _, t := range allTasks {
tasks <- t
}
close(tasks) // 必须在所有发送完成后 close
wg.Wait() // 等所有 worker 结束需要任务状态反馈?加一个结果 channel
如果调用方需要知道某个任务是否成功、耗时多久,不能只靠单向 chan。常见做法是让 worker 把结果发回另一个 channel,由主 goroutine 收集。
立即学习“go语言免费学习笔记(深入)”;
- 定义
type Result struct { ID string; Err error; Duration time.Duration } - 启动时多传一个
results := make(chan Result, len(allTasks)) - worker 处理完后写入:
results - 主 goroutine 用
for i := 0; i 收集(注意别用 <code>range,否则可能提前退出)
这种双 channel 模式比「给每个 task 分配 callback 函数」更符合 Go 的并发哲学:通过通信共享内存,而不是通过共享内存通信。
什么时候该换用第三方队列库
当出现以下情况,说明你已超出 chan 的适用边界:
- 需要持久化(崩溃后任务不丢),
chan全在内存里 - 要支持优先级、延迟投递、重试策略、死信队列
- 消费者数量动态伸缩(比如根据 CPU 负载增减 goroutine 数)
- 跨进程/跨机器分发任务(这时得上 Redis、RabbitMQ 或 NATS)
这时候别硬套 chan,直接上 asynq(Redis 后端)、machinery(支持多种 broker)或自建基于 pgx + LISTEN/NOTIFY 的 PostgreSQL 队列——channel 解决不了的问题,不该强行用 channel 解决。










