for...range内でgoroutineを起動する場合注意すること

5431 ワード

結論

for...rangeで取り出した値をgoroutineで使用する場合は関数の引数経由で参照する

実験

以下のようにfor...rangeで取り出した値をクロージャ内でそのまま参照すると、意図したものとは別のデータが参照されてしまう。
これはgoroutineの起動よりも早くループが回ってしまうために、クロージャ内では毎回tasksの最後の値が参照されてしまっている。

package main

import (
	"fmt"
	"time"
)

func main() {
	tasks := []string{
		"first!",
		"second!!",
		"third!!!",
	}
	for _, task := range tasks {
		go func() {
			fmt.Println(task)
		}()
	}
	time.Sleep(time.Second)
}
$ go run main.go
third!!!
third!!!
third!!!

以下のように引数経由で参照すれば解決できる。

package main

import (
	"fmt"
	"time"
)

func main() {
	tasks := []string{
		"first!",
		"second!!",
		"third!!!",
	}
	for _, task := range tasks {
		go func(task string) {
			fmt.Println(task)
		}(task)
	}
	time.Sleep(time.Second)
}
$ go run main.go
first!
second!!
third!!!