以下のように sync.WaitGroup
により goroutine を待つプログラムがあるとします。
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
wg.Add(1)
go func(wg sync.WaitGroup) {
fmt.Println("Sleeping...")
time.Sleep(1 * time.Second)
fmt.Println("Awaken!")
wg.Done()
}(wg)
wg.Wait()
fmt.Println("Done")
}
一見問題なさそうですが、実行してみると deadlock が検出されます。
$ go run main.go
Sleeping...
Awaken!
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [semacquire]:
sync.(*WaitGroup).Wait(0x2081a0020)
/usr/local/Cellar/go/1.4.2/libexec/src/sync/waitgroup.go:132 +0x169
main.main()
/Users/yuya/src/github.com/yuya-takeyama/go-practice/main.go:20 +0x7a
exit status 2
これは sync.WaitGroup
を持った変数 wg
を値渡しで goroutine に渡すことによってコピーが発生するためです。
以下のようにポインタ渡しにすることで解決できます。
(main 以外は省略します。)
func main() {
wg := new(sync.WaitGroup)
wg.Add(1)
go func(wg *sync.WaitGroup) {
fmt.Println("Sleeping...")
time.Sleep(1 * time.Second)
fmt.Println("Awaken!")
wg.Done()
}(wg)
wg.Wait()
fmt.Println("Done")
}
実行します。
$ go run main.go
Sleeping...
Awaken!
Done
問題なく実行を完了できました。
もしくは goroutine からクロージャとして変数 wg
を参照しても良いでしょう。
func main() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
fmt.Println("Sleeping...")
time.Sleep(1 * time.Second)
fmt.Println("Awaken!")
wg.Done()
}()
wg.Wait()
fmt.Println("Done")
}
実行結果は同じなので省略。
実際は goroutine の中で別の関数を呼び出すときにうっかり値渡ししてしまうことが多いかも。