💻

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

2022/04/16に公開2

結論

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!!!

Discussion

SpiegelSpiegel

task := task で shadowing する手もあります。

package main

import (
    "fmt"
    "time"
)

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

https://go.dev/play/p/49nvpxn0uN0

morrismorris

なるほど、goroutine起動前にtaskの値を確定させてしまえばいいんですね。
go言語勉強中なので参考になります!