[golang]ErrGroupでgoroutineを簡単に使える

今回はもっと便利にgoroutineを使用できるErrGroupパッケージについてまとめてみます。
前回に引き続き、Goの並行処理に親しみたいがテーマです。
自身の学習も兼ねて、社内勉強会で紹介します。

まえおき

前回はGoの並行処理に関わるchanelなどを用いて簡単なスケジューラを作って紹介しました。
私自身が並行処理について理解を深めたいがためにプリミティブ?な機能が中心になりましたが、Goには並行処理をもっと便利に扱えるErrGroupがあります。
今回はこちらの紹介をしながら、並行処理を実装するイメージを掴んでいけるような内容にしたいと思います。

並行処理についてざっくりおさらい

なに
あるタスクを完結させるのにA,B,Cの処理が必要とします。
通常はA→(A完了)→B→(B完了)→C→(C完了)と逐次実行させます。
これを並行処理にすると、以下のように同時っぽく処理ができます。
A→(A完了)
B→(B完了)
C→(C完了)

うれしいこと
外部サービスへの問い合わせやDBアクセスなど、重い処理の実行を1個1個待たずに行えます。(レスポンスを待ちぼうけする時間を短縮)
上の例で言うと、ABCそれぞれレスポンスに1秒かかるAPIを呼ぶとしたとき、
逐次処理ではどうあがいても3秒かかるところ、並行処理だと早ければ1秒ちょっとで済むことが期待できます。

ErrGroupについて

並行処理のイメージで以下のように表現しましたが、実際にはそれぞれの処理が全て完了して初めて1つのタスクとして完結します。
A→(A完了)
B→(B完了)
C→(C完了)
つまり全ての処理が終了するまで待ち受ける事が必要になります。
これを簡単に行えるのがsync.errgroupパッケージです。
golang.org/x/sync/errgroup

ちなみにorgがついているのはGoでは準標準的なパッケージとされています。
標準パッケージのsyncにもWaitGroupというものがあります。
pkg.go.dev/sync#WaitGroup

errgroupは上記のWaitGroupにエラーハンドリングを追加したようなものになっています。
WaitGroupでは通常、並行処理内でエラーが発生したかどうかを検知できません(別途自力で実装する必要があります)。
errgroupではその点に対応できるのが便利です。

使い方

以下の例は公式のExampleから持ってきています。
https://pkg.go.dev/golang.org/x/sync@v0.4.0/errgroup#example-Group-JustErrors
少し解説します。

func main() {
g := new(errgroup.Group)
var urls = []string{
"http://www.golang.org/",
"http://www.google.com/",
"http://www.エラー出るよ.com/",
}
for _, url := range urls {
url := url
g.Go(func() error { // ①
resp, err := http.Get(url)
if err == nil {
resp.Body.Close()
}
return err // ②
})
}
if err := g.Wait(); err != nil { // ③
fmt.Printf("エラー出たよ: %v", err)
return
}
fmt.Println("完了")
}


通常のgoroutineの構文は go f() ですが、
errgroupでは g.GO(f()) でgoroutineを起動します。引数に関数を渡すのが特徴ですね。


resp, err := http.Get(url) の結果にエラーがあった場合はここで返却されます。
エラーがない場合は err == nil となります。


g.Wait() で全てのgoroutineが終了するまで待ち受けます。
エラーが複数あった場合は最初のエラーを返します。

その他の機能

errgroupには他にも便利な機能がありますので軽く紹介します。

WithContext
errgroupをnewするときにcontextも同時に生成してくれます。
例) g, ctx := errgroup.WithContext(context.TODO())
各goroutineにcontextを渡して、どれか1つの処理でエラーがでたときにcontextをキャンセルするようにしておけば、他のgoroutineの処理を中断させることができます。

SetLimit
同時に起動するgoroutineの最大数を設定できます。
一杯になったら空きができるまでgoroutineの起動がブロックされます。
CPUのコア数以上にgoroutineを立ててもあまり意味がないらしいので、こういった制限はよく使われると思います。

おわりに

今回はgoroutineとerrgroupについてまとめてみました。
Goで並行処理を実装するイメージが掴めていたらうれしいです。

参考

Go の goroutine / channel は全然簡単じゃないので errgroup を使おう

golang.org/x/sync/errgroup
pkg.go.dev/sync

前へ

新人研修課題を通して学んだことと今後の展望

次へ

【FastAPI】FastAPIによるお手軽!型定義共有