概要
例えば並列で 5 つの goroutine を回す場合、すべての goroutine の終了を待ってからメインの処理に戻りたい場合があると思います
今回は sync パッケージと errgroup パッケージを使った 2 つのサンプルコードを紹介します
環境
- macOS 10.14.1
- golang 1.11.2
- errgroup 42b3178
sync.waitGroup
sync パッケージはデフォルトで使えるライブラリです
これでも goroutine の並列処理の完了を待つことができます
mkdir -p go/src/github.com/hawksnowlog/eg_test/
touch go/src/github.com/hawksnowlog/eg_test/main.go
vim go/src/github.com/hawksnowlog/eg_test/main.go
package main
import (
"fmt"
"sync"
"time"
)
func waiter(i int) {
t := time.Duration(i)
time.Sleep(t * time.Second)
fmt.Println(i)
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
waiter(i)
}(i)
}
wg.Wait()
fmt.Println("end")
}
go build github.com/hawksnowlog/eg_test
go install github.com/hawksnowlog/eg_test
$GOPATH/bin/eg_test
これで 0 から 4 までのカウントが終了してから end が表示されると思います
まず sync.WaitGroup
の変数を作成します
作成した waitGroup の変数に対して goroutine を開始する前に wg.Add(1)
でインクリメントします
そして goroutine 内で defer を使って関数が終了した際に必ず wg.Done()
をコールします
あとは同期したい部分で wg.Wait()
することですべての goroutine が終了するまで待機します
errgroup インストール
go get -u golang.org/x/sync/errgroup
errgroup.Group
同じようなことを errgroup.Group
を使ってもできます
こちらを使ったほうが簡潔に書けます
package main
import (
"fmt"
"golang.org/x/sync/errgroup"
"time"
)
func waiter(i int) error {
t := time.Duration(i)
time.Sleep(t * time.Second)
fmt.Println(i)
return nil
}
func main() {
eg := errgroup.Group{}
for i := 0; i < 5; i++ {
val := i
eg.Go(func() error {
return waiter(val)
})
}
err := eg.Wait()
if err != nil {
fmt.Println(err)
}
fmt.Println("end")
}
goroutine でコール関数が error を返却する必要があります
メイン処理では各 goroutine が error を返却しているかどうか判定することができるようになっています
また Add や Done の記述も不要になっています
ちなみに val := i
で別の変数に格納してから goroutine に値を渡さないとすべて 5 で値が渡ってしまうので注意が必要です
最後に
goroutine とメイン側の処理を同期する方法を紹介しました
機能的には errgroup のほうがいろいろできそうです
単純な同期であれば sync で十分かもしれません
今回のサンプルは goroutine 内のタイムアウトを考慮できていません
チャネルなどと組み合わせるか goroutine で呼び出している関数側でタイムアウト時の処理を考慮するようにしましょう
0 件のコメント:
コメントを投稿