Go
goroutine
平行処理

Go言語でクロージャと goroutine を組み合わせた時に起こること

この記事は go 公式 FAQ What happens with closures running as goroutines? の翻訳です。

クロージャと並行処理を組み合わせる時よく混乱が起こります。次のコードを見てみましょう:

func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

    // wait for all goroutines to complete before exiting
    for _ = range values {
        <-done
    }
}

間違って、上のプログラムの出力が a b c になると予想する人がいるかもしれません。しかし、実際は c c c になります。これは、ループの各イテレーションは同じ変数 v を参照するためです。つまり、各クロージャは同一の変数を共有しています。クロージャが実行される時、fmt.Println の実行によって v を出力しますが、その v は goroutine がローンチされた以降に変更されているかもしれないのです。go vet コマンドを使うと、この種の問題等を事前に検出することが出来ます。

ループの各 v の値をクロージャに対してバインドするためには、ループの中で新しい変数が作られるように修正しなければなりません。一つの方法は、変数をクロージャのパラメータとして渡す方法です:

    for _, v := range values {
        go func(u string) {
            fmt.Println(u)
            done <- true
        }(v)
    }

この例では、v の値は無名関数のパラメータとして渡されています。この関数の中では、その値に対して変数 u としてアクセスすることが出来ます。

より簡単な方法は、単に新しい変数を宣言する方法です。この記法は一見変に見えるかもしれませんが、Go 言語においては問題のない記法です:

    for _, v := range values {
        v := v // create a new 'v'.
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }