go的errgroup和协程池

errgroup:适合需要统一错误处理和上下文取消的批量任务,但不控制并发数。
协程池:适合需要严格限制并发数、复用 goroutine 资源的场景。

errgroup

一、errgroup 的核心功能

  1. 并发任务管理
import "golang.org/x/sync/errgroup"

func main() {
    var g errgroup.Group
    
    // 启动多个并发任务
    g.Go(func() error {
        // 任务 1
        return nil
    })
    
    g.Go(func() error {
        // 任务 2
        return errors.New("task failed")
    })
    
    // 等待所有任务完成,任一出错则返回首个错误
    if err := g.Wait(); err != nil {
        fmt.Println("Error:", err)
    }
}
  1. 上下文取消
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()

var g errgroup.Group
g.Go(func() error {
    select {
    case <-ctx.Done():
        return ctx.Err()
    default:
        // 执行任务
    }
    return nil
})

在这里插入图片描述
何时需要真正的协程池?

当遇到以下情况时,推荐使用协程池(如 ants):

1.大量短任务:频繁创建 / 销毁 goroutine 导致性能开销。

// ants 示例:限制 100 个 goroutine
pool, _ := ants.NewPool(100)
defer pool.Release()

for i := 0; i < 1000; i++ {
    pool.Submit(func() {
        // 短任务逻辑
    })
}

2.资源限制:防止无限创建 goroutine 导致 OOM(内存溢出)。

// 限制 goroutine 数量为 CPU 核心数
pool, _ := ants.NewPool(ants.DefaultAntsPoolSize)

3.任务队列:需要排队等待执行的任务(如生产消费模型)。

errgroup协程的生命周期

errgroup 创建的 goroutine 的生命周期取决于以下因素:
一、正常完成的 goroutine

当 errgroup.Go() 中的函数 正常返回(无错误) 时,对应的 goroutine 会立即退出并被销毁:

g.Go(func() error {
    // 执行任务
    return nil // 任务完成 → goroutine 退出
})

关键点:
每个 g.Go() 会启动一个独立的 goroutine,该 goroutine 在函数返回后结束。

二、出错时的 goroutine 行为

当任一 goroutine 返回非 nil 错误 时:

errgroup.Wait() 会立即返回该错误。
其他未完成的 goroutine 不会自动停止,除非它们主动监听上下文取消信号。

示例(错误处理但未主动取消):

var g errgroup.Group
g.Go(func() error {
    time.Sleep(time.Second) // 模拟耗时操作
    return errors.New("task failed")
})

g.Go(func() error {
    time.Sleep(5 * time.Second) // 长时间操作
    return nil
})

if err := g.Wait(); err != nil {
    fmt.Println("Error:", err) // 立即返回第一个错误
}
// 第二个 goroutine 仍会继续执行 5 秒才结束

限制执行时间:
使用 context.WithTimeout 或 context.WithDeadline 避免无限等待:

ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

协程池的使用

  1. ants(最流行)

特性:高性能、支持定时任务、内存优化、监控指标。
GitHub:https://github.com/panjf2000/ants
2. gopool

特性:轻量级、简洁 API。
GitHub:https://github.com/go-co-op/gopool
3. workerpool

特性:灵活的任务分发、支持动态调整池大小。
GitHub:https://github.com/alitto/pond

基础用法:

package main

import (
    "fmt"
    "sync"
    "github.com/panjf2000/ants/v2"
)

func main() {
    // 创建固定大小为 100 的协程池
    pool, _ := ants.NewPool(100)
    defer pool.Release() // 释放资源

    var wg sync.WaitGroup

    // 模拟 1000 个任务
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        idx := i // 避免闭包捕获循环变量

        // 提交任务到协程池
        _ = pool.Submit(func() {
            defer wg.Done()
            fmt.Printf("Task %d executed by goroutine %d\n", idx, ants.GetCurrentGoroutineID())
        })
    }

    wg.Wait()
    fmt.Println("All tasks completed")
}

动态大小的:

// 创建可伸缩的协程池(最大 1000 个 goroutine)
pool, _ := ants.NewPool(1000, ants.WithExpiryDuration(10*time.Second))
// 空闲 goroutine 10 秒后自动销毁,池大小动态调整

协程池状态的获取:

pool.Running()    // 返回当前运行的 goroutine 数量
pool.Free()       // 返回空闲 goroutine 数量
pool.Cap()        // 返回池的容量
pool.Ready()      // 检查池是否已初始化
pool.Reduce()     // 手动缩减池大小

根据负载调整池大小:
CPU 密集型任务:池大小 = CPU 核心数(runtime.NumCPU())。
IO 密集型任务:池大小可远大于 CPU 核心数(如 1000+)。

设置任务超时:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

pool.Submit(func() {
    select {
    case <-ctx.Done():
        return
    default:
        // 任务逻辑
    }
})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值