Go Worker Pool 模式:用带缓冲 channel 把任务批处理与限流做稳

test12026-10-041 次阅读

为什么需要 Worker Pool

每来一个任务就开一个 goroutine,在突发流量下 goroutine 数量会失控,内存与调度都吃紧。Worker Pool 用固定数量的 worker 常驻消费任务队列,既限制并发上限,又复用 goroutine,天然带限流效果。

基础结构

type Job struct {
    ID   int
    Data string
}

type Result struct {
    JobID  int
    Output string
    Err    error
}

type Pool struct {
    jobs    chan Job
    results chan Result
    workers int
}

启动 worker

每个 worker 从 jobs 通道循环取任务,处理完写回 results,通道关闭时退出:

func (p *Pool) worker() {
    for job := range p.jobs {
        out, err := process(job)
        p.results <- Result{JobID: job.ID, Output: out, Err: err}
    }
}

func process(j Job) (string, error) {
    // 实际业务逻辑,例如调用下游服务
    return "done-" + j.Data, nil
}

派发与收集

主流程把任务投进 jobs,再从 results 收齐,用 sync.WaitGroup 等所有 worker 退出:

func (p *Pool) Run(tasks []Job) []Result {
    var wg sync.WaitGroup
    for i := 0; i < p.workers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            p.worker()
        }()
    }

    go func() {
        for _, t := range tasks {
            p.jobs <- t
        }
        close(p.jobs)
    }()

    var results []Result
    for r := range p.results {
        results = append(results, r)
    }

    wg.Wait()
    return results
}

用 context 控制取消

任务处理里监听 ctx.Done,上游超时时及时退出,避免资源空耗:

func process(ctx context.Context, j Job) (string, error) {
    select {
    case <-ctx.Done():
        return "", ctx.Err()
    case <-time.After(50 * time.Millisecond):
        return "done-" + j.Data, nil
    }
}

工程落地的几点

  • worker 数不是越大越好,通常贴近 CPU 核数或下游连接池上限。
  • results 通道要带缓冲,否则 worker 写回时会阻塞在派发协程上。
  • 需要限流时,把 jobs 投送速率用 time.Tick 控住即可,pool 本身不关心来源。
T

test1

文章作者

为什么需要 Worker Pool 每来一个任务就开一个 goroutine,在突发流量下 goroutine 数量会...

分类
技术
发布时间
2026-10-04
字数
约 938 字
阅读
1 次