ゴルーチンを使用して並行実行した後に、一度でもエラーがあれば、検知してハンドリングしたいと思います。
この記事ではどうすれば、この問題を解決できるかを紹介したいと思います。
解決策として、errgroupを使用します。
こちらを使用することで、ゴルーチンで並行実行している処理が1つでもエラーになった場合に、エラーを検知できるようになります。
コード
10回ループしてiを0からインクリメントして、iがコマンドライン引数と同じ値の場合に、エラーになる処理をGorutineで並行実行するようにしている。
package main
import (
"context"
"fmt"
"log"
"os"
"strconv"
"time"
"golang.org/x/sync/errgroup"
)
var NUM int
func printInt(num int) error {
log.Println(num)
if num == NUM {
return fmt.Errorf("Fail")
}
time.Sleep(3 * time.Second)
return nil
}
func egGo(eg *errgroup.Group, value int) {
eg.Go(func() error {
return printInt(value)
})
}
func main() {
NUM, _ = strconv.Atoi(os.Args[1])
eg, _ := errgroup.WithContext(context.TODO())
for i := 0; i < 10; i++ {
value := i
egGo(eg, value)
}
err := eg.Wait()
log.Println("Complete all task", err)
}
実行結果1(エラーが発生するパターン)
% go run main.go 1
2022/12/10 19:15:18 3
2022/12/10 19:15:18 7
2022/12/10 19:15:18 9
2022/12/10 19:15:18 1
2022/12/10 19:15:18 0
2022/12/10 19:15:18 8
2022/12/10 19:15:18 2
2022/12/10 19:15:18 4
2022/12/10 19:15:18 6
2022/12/10 19:15:18 5
2022/12/10 19:15:21 Complete all task Fail
1でエラーになるため、「Fail」が最後出力されていることがわかる。
実行結果2(エラーが発生しないパターン)
% go run main.go 20
2022/12/10 19:19:23 6
2022/12/10 19:19:23 0
2022/12/10 19:19:23 5
2022/12/10 19:19:23 2
2022/12/10 19:19:23 9
2022/12/10 19:19:23 4
2022/12/10 19:19:23 7
2022/12/10 19:19:23 8
2022/12/10 19:19:23 3
2022/12/10 19:19:23 1
2022/12/10 19:19:26 Complete all task <nil>
20ではエラーにならないので、先ほどFailだったのが<nil>になっている
注意点
複数のエラーが発生した場合は、最初エラーになった処理の結果が出力されるようになる。
なので並行実行する各処理の中で、エラーを出力するようにした方が良い。
例として、iが1 or 2の場合にエラーにする処理を書いて、エラーになった場合に「Fail:{エラーになった時のnum}」を返しています。
package main
import (
"context"
"fmt"
"log"
"time"
"golang.org/x/sync/errgroup"
)
func printInt(num int) error {
log.Println(num)
if num == 1 || num == 2 {
return fmt.Errorf("Fail:%d", num)
}
time.Sleep(3 * time.Second)
return nil
}
func egGo(eg *errgroup.Group, value int) {
eg.Go(func() error {
return printInt(value)
})
}
func main() {
eg, _ := errgroup.WithContext(context.TODO())
for i := 0; i < 10; i++ {
value := i
egGo(eg, value)
}
err := eg.Wait()
log.Println("Complete all task", err)
}
実行結果
% go run main.go
2023/03/09 08:22:26 0
2023/03/09 08:22:26 6
2023/03/09 08:22:26 5
2023/03/09 08:22:26 7
2023/03/09 08:22:26 8
2023/03/09 08:22:26 4
2023/03/09 08:22:26 1
2023/03/09 08:22:26 3
2023/03/09 08:22:26 2
2023/03/09 08:22:26 9
2023/03/09 08:22:29 Complete all task Fail:1
1が2より先に実行されているので結果が「Fail:1」となっている。
まとめ
各処理でエラーを出力する必要がありますが、処理を並行で実行して、全ての処理が完了するまで待ちたい場合は、sync.WaitGroupよりもerrgourpの方が、エラーのハンドリングを書かなくて済むので、使い勝手が良いと思いました。
Go言語について他にも、Goでmockを作成する方法や、selectの仕組みについての記事も書いてますので、そちらも見てもらえたらと思います。
【おすすめ記事のリンク】
コメント