LoginSignup
7
6

More than 5 years have passed since last update.

Go あるある: sync.WaitGroup を値渡しして goroutine が deadlock

Last updated at Posted at 2015-04-07

以下のように 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 の中で別の関数を呼び出すときにうっかり値渡ししてしまうことが多いかも。

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6