Go语言最常用、最值得优先掌握的五个并发设计模式是Fan-out/Fan-in、Worker Pool、Pipeline、Producer-Consumer、Batch Processing,它们均符合Go“通过通信共享内存”的哲学,能用channel自然表达、配合WaitGroup或context控制生命周期、避免裸用mutex。

Go语言最常用、最值得优先掌握的并发设计模式就五个:Fan-out / Fan-in、Worker Pool、Pipeline、Producer-Consumer、Batch Processing。它们不是理论概念,而是你写高并发服务时每天都会用到的实操结构。
为什么选这五个?——不是所有“模式”都值得投入时间
Go 的并发哲学是“通过通信共享内存”,所以真正落地有效的模式,必须满足三个条件:能用 channel 自然表达、能配合 sync.WaitGroup 或 context.Context 控制生命周期、避免裸用 mutex 做协调。这五个模式全部符合,其余诸如 “Single-Flight” 或 “Circuit Breaker” 属于业务层抽象,不属于 Go 并发原语范畴。
Fan-out / Fan-in:处理“多任务分发 + 结果聚合”的标准解法
当你需要把一批任务(比如 100 个 URL 抓取)并发分给多个 goroutine 处理,并把结果统一收回来,Fan-out / Fan-in 就是最直接的选择。它比手写 WaitGroup + slice 更安全,也比全塞进一个 channel 更可控。
- 扇出(Fan-out):用一个
for循环向多个 worker 的输入 channel 发送任务,或直接启动多个 goroutine 读同一个输入 channel - 扇入(Fan-in):用一个
merge函数把多个输出 channel 合并成一个 —— 切忌用for range直接遍历多个 channel,必须用select轮询或goroutine + channel中转 - 常见错误:忘记
close输入 channel,导致 worker 卡在range;或没等所有 worker 退出就关闭输出 channel,造成 panic
func merge(chs ...<-chan int) <-chan int {
out := make(chan int)
var wg sync.WaitGroup
wg.Add(len(chs))
for _, ch := range chs {
go func(c <-chan int) {
defer wg.Done()
for n := range c {
out <- n
}
}(ch)
}
go func() {
wg.Wait()
close(out)
}()
return out
}Worker Pool:控制并发数、防雪崩的刚需模式
如果你的程序要处理大量请求(如 HTTP 接口批量调用外部 API),不加限制地起 goroutine,很容易打爆下游或耗尽内存。Worker Pool 通过固定数量的长期 goroutine 消费任务队列,实现资源硬限流。
立即学习“go语言免费学习笔记(深入)”;
技术上面应用了三层结构,AJAX框架,URL重写等基础的开发。并用了动软的代码生成器及数据访问类,加进了一些自己用到的小功能,算是整理了一些自己的操作类。系统设计上面说不出用什么模式,大体设计是后台分两级分类,设置好一级之后,再设置二级并选择栏目类型,如内容,列表,上传文件,新窗口等。这样就可以生成无限多个二级分类,也就是网站栏目。对于扩展性来说,如果有新的需求可以直接加一个栏目类型并新加功能操作
- 核心结构:一个带缓冲的
jobs chan int+ N 个常驻workergoroutine + 一个sync.WaitGroup管理生命周期 - 关键细节:必须在发送完所有任务后
close(jobs),否则 worker 会永远阻塞在range jobs;如果任务有返回值,建议用单独的results chan,别和 jobs 混用 - 容易踩坑:用
make(chan int, 0)(无缓冲)当任务队列 —— 这会导致主 goroutine 在发第一个任务时就阻塞,除非同时有 worker 在读;正确做法是带缓冲,比如make(chan int, 100)
Pipeline 与 Producer-Consumer:什么时候该用哪个?
Pipeline 是一种数据流分阶段处理的建模方式(比如:解析 → 过滤 → 转换 → 存储),每个阶段是一个独立 goroutine,靠 channel 串起来;Producer-Consumer 更侧重角色分离(生产者只管发,消费者只管收),不要求阶段化。
- 选 Pipeline 当你需要清晰划分处理逻辑层级,且每阶段可能复用或替换(例如日志处理流水线)
- 选 Producer-Consumer 当你明确区分 IO 与 CPU 密集型任务(如:文件读取器 producer + JSON 解析器 consumer)
- 共同雷区:中间 channel 忘记 close,导致下游 goroutine 永远等不到 EOF;或某个阶段 panic 后未通知其他阶段退出,造成 goroutine 泄漏
- 实用技巧:用
context.WithCancel包裹整个 pipeline,一处 cancel 全链路退出
Batch Processing:简单但最容易被低估的模式
它看起来最“土”,却是处理大批量静态数据(如 CSV 导入、数据库迁移)时最稳的方式。比起泛滥的 “全量启 goroutine”,它用可控批大小(如每批 100 条)平衡吞吐与内存压力。
- 不要硬编码
batchSize := 100—— 应根据单条任务内存占用和 GC 压力动态估算,比如处理大 struct 时批大小应 ≤ 10 - 每个 batch 启一个 goroutine 是 OK 的,但记得用
sync.WaitGroup等待全部完成;别用for i := range batches { go process(batches[i]) }—— 这里i是循环变量,闭包捕获的是同一地址,大概率全处理错批次 - 若某批失败,是否重试?是否跳过?这些策略不在模式本身,但必须在
process函数里显式处理,别指望上层兜底
真正难的从来不是“知道有哪些模式”,而是面对一个新需求时,能快速判断:该用 Fan-in 收集结果,还是用 Worker Pool 控制并发,抑或干脆 Batch Processing 更稳妥。这种判断力,来自对每个模式边界和代价的切身理解 —— 比如你知道 merge 函数里那个 close(out) 必须放在 wg.Wait() 之后,否则下游 range 会 panic;你也知道 jobs channel 不 close,worker 就永远不会退出。这些细节,才是并发不出错的关键。









