Goにおいてクロージャとは、周囲のスコープの変数を「閉じ込める」(キャプチャする)関数のことです。特に、クロージャは他の関数内で作成される無名関数でよく使用されます。
クロージャを使用する際の最も一般的な問題は、ループ内の変数をゴルーチンで使用する際の予期しない動作です:
for i := 0; i < 3; i++ { go func() { fmt.Println(i) }() }
各ゴルーチンは同じ値のiを印刷する可能性があります。これは、変数iがループの中で変更され、クロージャがその変数をキャプチャするためです。
正しい方法:
for i := 0; i < 3; i++ { go func(val int) { fmt.Println(val) }(i) }
このような振る舞いは、クロージャが変数への参照(アドレス)を保持し、その値(バイ・バリュー)ではないために発生します。
ループ内でいくつかのゴルーチンが実行され、ループ変数をキャプチャした場合、どの値が印刷されるでしょうか?
回答: すべてのゴルーチンは同じ値(多くの場合は最後の値)を印刷します。これは、ゴルーチンが変数の現在の値を参照しており、ゴルーチンが実行される時点ではループが終了しているためです。これを回避するには、変数をクロージャへのパラメータとして渡す必要があります。
例:
for i := 0; i < 5; i++ { go func() { fmt.Println(i) }() } // おそらく出力されるのは: 5 5 5 5 5
物語
物語
物語
配送スタートアップでは、注文の座標を更新する際にクロージャが不適切に使用され、無名関数内のスライスへのアクセス競合により、最後の注文の座標が大量に更新されてしまいました。