👻

goroutineで気をつけること

2024/01/24に公開

for文で並行処理をしたい状況はたくさんあると思います。

var wg sync.WaitGroup
for i := 0; i < 10; i++ {
	wg.Add(1)
	go func() {
		defer wg.Done()
		fmt.Println(i)
	}()
}
wg.Wait()

上記のコードを実行すると、どのような結果が得られると思いますか?

5
10
10
10
7
10
10
10
10
10

実行するごとに、異なる結果が出ると思います。
これは、goroutineの中で変数iを表示するときにはすでに、forループが終了してしまっているからです。
これを回避するには、ループのスコープの中で新しい変数を宣言します。
この変更により、各ゴルーチンはそれぞれのvの値をキャプチャします。

var wg sync.WaitGroup
for i := 0; i < 10; i++ {
	v := i // 追加
	wg.Add(1)
	go func() {
		defer wg.Done()
		fmt.Println(v)
	}()
}
wg.Wait()

これを実行すると、期待通りの結果が得られることがわかります。

0
2
3
1
7
9
8
4
6
5

また、以下のように無名関数の引数として渡すことでも、解決することができます。

nums := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var wg sync.WaitGroup
for _, v := range nums {
	wg.Add(1)
	go func(num int) {
		defer wg.Done()
		fmt.Println(num)
	}(v)
}
wg.Wait()

Discussion