[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