Day 16|并发模式与实践(worker pool / fan-in / fan-out)[Go 语言 30 天系统学习计划]

学习目标

  • 理解并发模式在 Go 工程中的价值
  • 掌握常见并发模式:worker pool、fan-out、fan-in
  • 学会控制并发度,避免 goroutine 失控
  • 将 goroutine、channel、select、context 组合成可控并发结构

一、为什么需要并发模式

在前面的章节中,你已经学会了:

  • 如何启动 goroutine
  • 如何用 channel 通信
  • 如何用 select 做调度
  • 如何用 context 管理生命周期

但在真实工程中,问题往往不是“能不能并发”,而是:

  • 并发多少才合适?
  • 如何避免 goroutine 无限制增长?
  • 如何组织多个 goroutine 协作?

并发模式就是这些问题的成熟解决方案。


二、worker pool(工作池)模式

worker pool 是最常见、最重要的并发模式之一。

核心思想:

  • 预先启动固定数量的 worker goroutine
  • 通过 channel 分发任务
  • 限制并发度,防止资源耗尽

基本结构:

func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        results <- job * 2
    }
}

启动 worker pool:

jobs := make(chan int)
results := make(chan int)

for w := 0; w < 3; w++ {
    go worker(w, jobs, results)
}

说明:

  • worker 数量 = 并发度上限
  • jobs channel 是任务队列

三、worker pool 的工程要点

  • worker 数量通常与 CPU 核数或外部资源限制相关
  • jobs channel 通常由生产者关闭
  • results channel 常配合 WaitGroup 或 fan-in 使用

工程认知:worker pool 的本质是“并发限流”。


四、fan-out 模式(任务分发)

fan-out 指:

将一个输入流,分发给多个 goroutine 并行处理

示例:

for i := 0; i < 3; i++ {
    go func() {
        for job := range jobs {
            fmt.Println("process job:", job)
        }
    }()
}

特点:

  • 多个 goroutine 同时从同一个 channel 读取
  • 每个任务只会被其中一个 goroutine 处理

工程场景:任务并行处理、请求并发执行。


五、fan-in 模式(结果汇聚)

fan-in 指:

将多个输入 channel 合并为一个输出 channel

示例结构:

func fanIn(cs ...<-chan int) <-chan int {
    out := make(chan int)

    for _, c := range cs {
        go func(ch <-chan int) {
            for v := range ch {
                out <- v
            }
        }(c)
    }

    return out
}

说明:

  • fan-in 常用于汇聚多个 worker 的结果
  • 通常需要配合 close 控制生命周期

六、fan-in 的关闭问题(非常重要)

一个关键问题是:

什么时候关闭 out channel?

常见做法:

  • 使用 sync.WaitGroup 统计输入 channel 完成情况
  • 所有输入处理完成后,再关闭 out

工程结论:

fan-in 中,关闭 channel 的责任必须非常清晰,否则极易 panic 或泄漏。


七、并发模式 + context 的组合

在现代 Go 工程中:

  • worker pool 几乎一定要支持 context 取消
  • 一旦请求取消,所有 worker 都应尽快退出

示例结构:

func worker(ctx context.Context, jobs <-chan int) {
    for {
        select {
        case <-ctx.Done():
            return
        case job, ok := <-jobs:
            if !ok {
                return
            }
            fmt.Println("job:", job)
        }
    }
}

工程认知:并发模式必须“可中断、可回收”。


八、常见并发模式误区

  • 无限启动 goroutine,不做并发限制
  • fan-out 后忘记 fan-in,导致 goroutine 泄漏
  • channel 关闭职责不清晰
  • 没有 context,任务无法取消

九、并发模式的工程定位总结

  • worker pool:控制并发度
  • fan-out:并行化处理
  • fan-in:结果汇聚
  • context:生命周期控制

一句话总结:

并发不是“同时做很多事”,而是“可控地同时做正确的事”。


预告:Day 17|并发安全与数据竞争(race condition)

下一天将系统讲解:

  • 什么是数据竞争
  • Go race detector 的使用
  • 常见并发 bug 形态
  • 如何写出 race-free 的 Go 代码

发表评论