Go での非同期処理。まちあわせ。
前回 Go での非同期処理 その1でも、channel を使った待ち合わせの方法を実装したが、もっとちゃんとしたやり方がある様子。
WaitGroup を使う
WaitGroup というのを使うみたいだが、使うときと使わないときの比較をしてみよう。
package main
import(
"fmt"
"time"
)
func wait(i int) {
time.Sleep(time.Second)
fmt.Println(fmt.Sprintf("Wait %d done!", i))
}
func main() {
for i := 0; i < 3; i++ {
go wait(i)
}
fmt.Println("Everything Done!")
}
こんなコードを書いてみる。当たり前だが、go routine が動くが、終わる前にメインの処理が終わってしまうので、次のような実行結果になる。
$ go run cmd/wait/*.go
Everything Done!
じゃあ、これをちゃんと、WaitGroup というやつに対応させてみよう。
package main
import (
"fmt"
"sync"
"time"
)
func wait(wg sync.WaitGroup, i int) {
defer wg.Done()
time.Sleep(time.Second)
fmt.Println(fmt.Sprintf("Wait %d done!", i))
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go wait(wg, i)
}
wg.Wait()
fmt.Println("Everything Done!")
}
基本は、WaitGroupを共有して、呼び元で、WaitGroup の Add メソッドを Go Routine の数だけ
呼んであげて、go routine の方で、Done()
を呼び出せば、WaitGroup が終了を検知できるしくみになっている。さあ実行
W
io/WaitGo
$ go run cmd/wait/*.go
Wait 1 done!
Wait 0 done!
Wait 2 done!
fatal error: all goroutines are asleep - deadlock!
あかんがな、実行できているけど、Deadlock やがな。調べてみると、WaitGroup を渡すのに、実体を渡していると、関数にわたるときに、コピーされる。つまり、いくら Go Routine のほうで Done() を呼び出したところで、コピーのほうのDone() が呼ばれているだけで、元の、WaitGroup で足した Add の数が減ることがないという現象。じゃあ、ポインタ渡しにかえよう。
package main
import (
"fmt"
"sync"
"time"
)
func wait(wg *sync.WaitGroup, i int) {
defer wg.Done()
time.Sleep(time.Second)
fmt.Println(fmt.Sprintf("Wait %d done!", i))
}
func main() {
wg := &sync.WaitGroup{}
for i := 0; i < 3; i++ {
wg.Add(1)
go wait(wg, i)
}
wg.Wait()
fmt.Println("Everything Done!")
}
実行結果
$ go run cmd/wait/*.go
Wait 0 done!
Wait 2 done!
Wait 1 done!
Everything Done!
無事待ち合わせ Done!