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