Goのloopclosure問題、

問題というよりも、タイミングがそうだからそうなるよねという話です。

Goのgoroutineでの話で、以下のソースをVScodeで読み込ませるとopenclosureというwarningが出ます。実行結果も期待される値ではなくて、例えば全部40とかになるのですが、なぜか?

それは無名関数に明示的に引数としてvを与えてる訳ではないので、無名関数の中でvの使われる時点(v*2)のv値が使われるからです。具体的には並行処理を起動しても、すぐに各ルーチンが実行される訳でもなくて実行タイミングには遅延があるだろうから、実はvを使おうとしたタイミングではすでにfor range処理が先行したり、あるいは終了している可能性もあるから。

対応方法はv無名関数に引数として渡すのがスマートなやり方になるでしょう。

https://github.com/mushahiroyuki/lgo/blob/main/example/ch10/ex1005.go

package main

import "fmt"

func main() {
	a := []int{2, 4, 6, 8, 10, 12, 14, 16, 18, 20} //liststart
	ch := make(chan int, len(a))
	for _, v := range a {
		go func() {
			ch <- v * 2
			fmt.Println(":", v)
		}()
	}
	for i := 0; i < len(a); i++ {
		fmt.Print(<-ch, " ")
	} //listend
	fmt.Println()
}

こうすれば、無名関数が不規則なタイミングでその時点のv値を使用することなく、意図した値を引き渡すことができます。別の対応方法としてはvをシャドーイング(v := vをfor rangeの直後に挿入)しても良さそうですが。いずれにしろ並行処理の実行順序は保証されないので、結果の並びは昇順にはなりません。

		go func(v int) {
			ch <- v * 2
			fmt.Println(":", v)
		}(v)

 

admin