LoginSignup
12

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-05-05

この記事は 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
        }()
    }

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
12