3

Golang のドキュメントで典型的なデータ競合を見ていますが、このプログラムに問題がある理由がよくわかりません。

func main() {
    var wg sync.WaitGroup
    wg.Add(5)
    for i := 0; i < 5; i++ {
        go func() {
            fmt.Println(i) // Not the 'i' you are looking for.
            wg.Done()
        }()
    }
    wg.Wait()
}

印刷5, 5, 5, 5, 5すると予想されるときに印刷されます0, 1, 2, 3, 4(必ずしもこの順序であるとは限りません)。

私の見方では、ゴルーチンがループ内で作成されると、 の値iがわかっています (たとえば、log.Println(i)ループの先頭で a を実行して、期待値を確認できます)。したがって、ゴルーチンiが作成時の値を取得し、後でそれを使用することを期待しています。

明らかにそれは何が起こっているのかではありませんが、なぜですか?

4

3 に答える 3

8

関数リテラルはi、外側のスコープから を参照します。の値を要求するとi、現在の値が取得されますi。Go ルーチンが作成された時点での値を使用するにはi、引数を指定します。

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

実行可能な例

于 2013-06-21T15:28:12.490 に答える
2

変数iは関数リテラル内で宣言されていないため、クロージャーの一部になります。クロージャを理解する簡単な方法は、クロージャをどのように実装できるかを考えることです。簡単な解決策は、ポインターを使用することです。関数リテラルはコンパイラによっていくつかに書き換えられていると考えることができます

func f123(i *int) {
        fmt.Println(*i)
        wg.Done            
}
  • この関数を呼び出すと、go ステートメントによって、i変数のアドレスが呼び出された f123 (コンパイラによって生成された名前の例) に渡されます。

  • おそらくデフォルトの GOMAXPROCS==1 を使用しているため、ループは I/O やチャネル操作などの他の「スケジュール ポイント」を行わないため、for ループはスケジューリングなしで 5 回実行されます。

  • でループが終了するとi == 5wg.Waitfinally は 5 つの実行準備が整ったゴルーチン (f123 の場合) の実行をトリガーします。もちろん、それらはすべて同じ整数変数への同じポインタを持っていますi

  • すべてのゴルーチンが同じi値の 5 を認識するようになりました。

GOMAXPROCS > 1 で実行した場合、またはループが制御を譲った場合、異なる出力が得られる場合があります。これは、たとえばruntime.Goschedによっても実行できます。

于 2013-06-21T15:32:01.760 に答える
0

他の人が述べたように、変数iは作成したゴルーチン内で使用されますが、ループがすでにループを終了すると、それらのゴルーチンは将来的に実行される可能性があります。この時点で、の値はiis not5であり、すべての go ルーチンが開始され、i(as 5) の値を読み取り、陽気な方法で続行します。

FUZxxl は、値iを引数として関数に渡すことについて言及したと思います。これは、かなり複雑なシステム、特に go ルーチンを実行している関数がインライン クロージャでない場合に適していると思います。ただし、ほとんどの場合、go ルーチンごとに新しい一時変数を作成する方がずっとクリーンだと思います。

http://play.golang.org/p/6dnkrEGfhn

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

効果は同じであり、好みの問題であると主張することができます。これは私の好みです:p

于 2013-06-22T09:58:37.533 に答える