golang 基礎之 errgroup

Golang 的擴展併發庫 golang.org/x/sync/errgroup 提供了對多協程任務進行管理和錯誤處理的便利功能。

與基礎的 sync.WaitGroup 相比,errgroup.Group 在等待所有任務完成的同時,還會自動捕獲第一個非 nil 錯誤並返回。如果通過 WithContext 創建 Group,當任一子任務返回錯誤時,errgroup 會取消關聯的 Context,從而通知其他協程提前退出。

簡言之,errgroup 封裝了錯誤傳播、上下文取消和併發控制等功能,使併發編程更加簡潔易用。

基本用法

使用 errgroup時,首先需要創建一個Group實例。可以直接用零值初始化:var g errgroup.Group,或者調用errgroup.WithContext(ctx)同時獲取一個基於ctx派生的新Context。典型的用法是對每個併發任務調用g.Go(func() error)來啓動 goroutine。Group.Go方法內部會自動執行WaitGroup.Add(1),並在函數返回時執行Done()。最後使用g.Wait()等待所有任務完成:若有任務返回了錯誤,Wait 會返回該第一個非 nil 錯誤,否則返回 nil。廢話不多說,先滿上:

import (
    "context"
    "fmt"
    "net/http"
    "golang.org/x/sync/errgroup"
    "io/ioutil"
)
func main() {
    // 創建帶取消功能的 errgroup 和 Context
    g, ctx := errgroup.WithContext(context.Background())
    urls := []string{
        "https://example.com",
        "https://example.org",
        "https://example.net",
    }
    // 用於收集響應結果的切片
    results := make([]string, len(urls))
    for i, url := range urls {
        i, url := i, url  // 捕獲循環變量
        g.Go(func() error {
            // 在請求中使用 errgroup 的 Context,以支持取消
            req, _ := http.NewRequest("GET", url, nil)
            req = req.WithContext(ctx)
            resp, err := http.DefaultClient.Do(req)
            if err != nil {
                return err
            }
            defer resp.Body.Close()
            data, _ := ioutil.ReadAll(resp.Body)
            results[i] = string(data)
            return nil
        })
    }
    // 等待所有併發任務完成
    if err := g.Wait(); err != nil {
        fmt.Println("執行過程中發生錯誤:", err)
        return
    }
    fmt.Println("所有請求完成,結果:", results)
}

此外,errgroup.Group 提供了併發限制的功能。可以使用 g.SetLimit(n) 設置最多同時運行的協程數,配合 g.TryGo(f) 在不超限時才啓動任務。像這樣:

var g errgroup.Group
g.SetLimit(5)  // 限制最多 5 個併發 goroutine
for i := 0; i < 10; i++ {
    g.TryGo(func() error {
        // 模擬任務
        fmt.Println("任務執行")
        return nil
    })
}
if err := g.Wait(); err != nil {
    fmt.Println("執行出錯:", err)
} else {
    fmt.Println("所有任務成功完成")
}

示例

常見錯誤

errgroup vs sync.WaitGroup 的區別

應用

在真實項目中,errgroup 常用於在業務邏輯中並行執行若干相互獨立的子任務。

例如,在一個 HTTP 服務的處理函數中,可能需要並行調用多個後端服務或查詢不同數據源,然後整合結果返回給客戶端。

可以這樣組織代碼:

在請求對應的 Context 上創建一個 errgroup,爲每個獨立任務調用 g.Go(),並在最後通過 g.Wait() 收集結果或錯誤。如果某一任務出錯,關聯的 Context 會自動取消,其它任務會及時退出,從而避免不必要的工作。

舉個🌰:

func handler(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    g, ctx := errgroup.WithContext(ctx)
    var resA, resB string
    // 並行執行兩個調用
    g.Go(func() error {
        data, err := serviceA(ctx, ...)
        if err == nil {
            resA = data
        }
        return err
    })
    g.Go(func() error {
        data, err := serviceB(ctx, ...)
        if err == nil {
            resB = data
        }
        return err
    })
    if err := g.Wait(); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    fmt.Fprintf(w, "Results: %s, %s", resA, resB)
}

總的來說,errgroup 使得 Go 併發編程更易於編寫健壯的代碼,在出現錯誤時能夠優雅地停止不必要的工作。

注意事項

標題:errgroup 詳解
作者:mooncakeee
地址:http://blog.dd95828.com/articles/2025/05/20/1747701267973.html
聯繫:scotttu@163.com

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Ob9ZWC8j0_MpDy5nQSM7-Q