用chan传任务而非全局变量或锁,因go默认不信任共享内存,chan天然规避加锁、竞态、漏unlock等错误;jobs和results通道必须有缓冲(如make(chan task, 100)),否则主goroutine会在results阻塞。

为什么用 chan 而不是全局变量或锁来传任务?
因为 Go 的并发安全模型默认不信任共享内存。用 chan 传递任务,天然规避了 mutex 加锁、竞态检测、忘记 unlock 等常见错误;而全局变量 + sync.Mutex 在 worker 数量上升时极易漏锁或死锁。
实际写法中,jobs 和 results 两个通道必须是**有缓冲的**(比如 make(chan Task, 100)),否则一旦所有 worker 都在处理、没人读 results,主 goroutine 就会在 results 处永久阻塞。
- 无缓冲通道只适合同步信号(如“准备就绪”),不适合任务流
- 缓冲大小要大于峰值并发任务数,但别设成
math.MaxInt—— 内存会爆 - 如果任务结构体很大(比如含 []byte),考虑传指针
*Task减少拷贝
worker 函数里为什么不能直接用 range 读 jobs?
可以,但前提是主 goroutine 明确调用 close(jobs)。否则 for job := range jobs 会永远等待新任务,导致 worker 协程无法退出 —— 这在需要优雅关闭的系统里是致命缺陷。
更稳妥的做法是带 ok 判断:
for {
job, ok :=
-
range本质是隐式做ok检查,但隐藏了退出时机控制权 - 若 worker 还需做清理(如关闭数据库连接),必须用显式
ok才能插入 cleanup 逻辑 - 某些场景下(比如 worker 需响应中断信号),甚至要用
select+donechannel 主动退出
如何让多个 worker 的结果按提交顺序输出?
Go 的 channel 本身不保证“谁先发谁先到”,尤其当 worker 处理时间不均时,results 通道收到的顺序和任务提交顺序大概率不一致。想严格保序,得加一层索引标记:
type Result struct {
TaskID int
Output string
}
主 goroutine 启动时维护一个 map 或 slice 存放待收结果,用 TaskID 做 key;收到 Result 后填入对应位置,最后按 ID 顺序打印。
- 别依赖
results的接收顺序 —— 这是并发模型的基本常识,但新手常踩 - 如果业务允许乱序(如日志聚合、统计计数),反而该主动放弃保序,提升吞吐
- 真要强保序且任务量大,建议用
sync.WaitGroup+ 有序 slice,比 channel + map 更省内存
关闭通道前忘了 close(jobs) 会发生什么?
主 goroutine 发完所有任务后没关 jobs,worker 会一直卡在 上,整个程序无法退出 —— 这是最常见的 goroutine 泄漏来源之一。
正确流程必须是:发完任务 → close(jobs) → 等待所有 worker 结束 → 关 results(可选)。
- 只能由发送方 close channel,否则 panic
- close 后再往
jobs写会 panic,所以务必确保所有发送已完成 - 用
sync.WaitGroup等待 worker 退出比单纯依赖 channel 关闭更可靠
真正难的不是写对第一版,而是让这套逻辑在任务失败、worker panic、网络超时等异常路径下依然能 clean exit —— 这部分没被示例覆盖,但生产环境绕不开。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











