如何使用Golang实现goroutine并发执行_Golang goroutine并发控制方法汇总

goroutine泄漏典型表现为内存持续上涨且runtime.NumGoroutine()只增不减;常见原因包括channel未关闭致接收阻塞、ticker未Stop;可通过pprof查看堆栈,检查for range ch是否确保channel关闭,以及ticker是否配对Stop。

如何使用golang实现goroutine并发执行_golang goroutine并发控制方法汇总

goroutine 泄漏的典型表现和快速定位方法

启动大量 goroutine 但程序内存持续上涨、runtime.NumGoroutine() 返回值只增不减,基本可判定存在 goroutine 泄漏。常见原因是 channel 未关闭导致接收方永久阻塞,或 timer/ticker 未 Stop()

  • pprof 查看 goroutine 堆
    curl 'http://localhost:6060/debug/pprof/goroutine?debug=2'
  • 检查所有 for range ch 循环——必须确保 ch 会被关闭,否则接收协程永远卡在 recv 状态
  • 所有 time.Ticker 必须配对 ticker.Stop(),尤其在 select + case 场景下

用 sync.WaitGroup 控制固定任务集的并发完成

sync.WaitGroup 适合「已知数量、无需超时、不关心返回值」的并发场景,比如批量写文件、并行初始化服务。它不提供取消或错误传播能力。

  • 必须在 goroutine 启动前调用 wg.Add(1),不能在 goroutine 内部调用(竞态风险)
  • wg.Done() 应放在 defer 中,避免 panic 导致计数器未减少
  • 不要在循环里重复声明 var wg sync.WaitGroup——每次都要重置计数器,否则 Wait() 可能提前返回
var wg sync.WaitGroup
for _, url := range urls {
    wg.Add(1)
    go func(u string) {
        defer wg.Done()
        http.Get(u) // 实际逻辑
    }(url)
}
wg.Wait() // 阻塞直到全部完成

用 context.WithTimeout 管理带超时的并发请求

当并发调用外部 API 或数据库,并需要统一超时控制时,context.WithTimeout 是比 time.After 更安全的选择——它能主动取消子 goroutine 的阻塞操作(如 http.Client 支持 context)。

Pixie.haus

Pixie.haus

AI像素图像生成平台

下载

  • 不要把同一个 context.Context 传给多个独立 goroutine 后再调用 cancel()——这会同时中断所有,应为每个 goroutine 创建子 context
  • HTTP 请求务必使用 http.NewRequestWithContext(ctx, ...),原生 http.Get 不响应 cancel
  • ctx.Err() 在超时后为 context.DeadlineExceeded,可据此区分超时与其他错误
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

var wg sync.WaitGroup
for _, url := range urls {
    wg.Add(1)
    go func(u string) {
        defer wg.Done()
        req, _ := http.NewRequestWithContext(ctx, "GET", u, nil)
        client := &http.Client{}
        resp, err := client.Do(req)
        if err != nil {
            if errors.Is(err, context.DeadlineExceeded) {
                log.Printf("timeout for %s", u)
            }
            return
        }
        resp.Body.Close()
    }(url)
}
wg.Wait()

用 errgroup.Group 替代手写错误收集与取消

标准库 errgroup.Group 封装了 sync.WaitGroup + context + 错误传播,适用于「并发执行、任一失败即终止、需返回首个错误」的场景,比如微服务多依赖调用。

立即学习go语言免费学习笔记(深入)”;

  • 必须用 eg.Go(func() error { ... }) 启动任务,不能用 go 关键字直接起 goroutine
  • 首次非 nil 错误会自动触发 ctx.Cancel(),后续 goroutine 应检查 ctx.Err() 主动退出
  • 如果所有 goroutine 都没返回错误,eg.Wait() 返回 nil;否则返回第一个非 nil 错误
g, ctx := errgroup.WithContext(context.Background())
for _, task := range tasks {
    task := task // 避免循环变量捕获
    g.Go(func() error {
        select {
        case <-time.After(task.Delay):
            return nil
        case <-ctx.Done():
            return ctx.Err()
        }
    })
}
if err := g.Wait(); err != nil {
    log.Fatal(err) // 第一个出错的任务
}

实际并发控制最易被忽略的是:**goroutine 生命周期与资源释放的耦合关系**。比如开 goroutine 读 channel,却忘了 close;启了 http.Server 却没处理 Shutdown();用了 os.Pipe() 却没 close write end —— 这些都会让 goroutine 挂住,且很难通过日志察觉。

https://www.php.cn/faq/2024218.html

发表回复

Your email address will not be published. Required fields are marked *