この記事では、主に Golang の for ループと goroutine の問題に関する関連情報をサンプル コードを通じて詳しく紹介します。これは、Golang を学習したり使用したりする人にとって、一定の参考学習価値があります。以下に従ってください。私と一緒に学びましょう。
背景
最近、MIT の分散コース 6.824 を勉強しているときに、Go を使用して Raft プロトコルを実装するときにいくつかの問題に遭遇しました。皆さんの参考と勉強のために共有します。以下では多くを述べませんが、詳細な紹介を見てみましょう。
次のコードを参照してください:
for i := 0; i < len(rf.peers); i++ { DPrintf("i = %d", i) if i == rf.me { DPrintf("skipping myself #%d", rf.me) continue } go func() { DPrintf("len of rf.peers = %d", len(rf.peers)) DPrintf("server #%d sending request vote to server %d", rf.me, i) reply := &RequestVoteReply{} ok := rf.sendRequestVote(i, args, reply) if ok && reply.VoteGranted && reply.Term == rf.currentTerm { rf.voteCount++ if rf.voteCount > len(rf.peers)/2 { rf.winElectionCh <- true } } }() }
その中で、ピアスライスの長さは 3 であるため、最も高い添え字は 2 です。コード内の for ループは、非並列プログラミングにおいて非常に直感的であるはずです。何も問題があったことに気づきませんでした。ただし、デバッグ プロセス中に、インデックス範囲外エラーが報告され続けました。デバッグ情報によると、i の値は 3 でした。その時点では、明らかに i
分析
何が起こったのか理解できませんが、ループ内に導入されたゴルーチンが原因であることはわかっています。 Google で検索したところ、Go wiki に Common Mistake - using goroutines onoop iterator variables というページがあり、この問題について具体的に言及していることがわかりました。とてもよくあることのようです~
初心者はよく次のコードを使用します。並列化するには データを処理します:
for val := range values { go val.MyMethod() }
またはクロージャを使用します:
for val := range values { go func() { fmt.Println(val) }() }
ここでの問題は、val が実際にはスライス内のすべてのデータを反復する単一の変数であることです。クロージャはこの val 変数にのみバインドされているため、上記のコードの結果として、すべてのゴルーチンがスライスの最後の要素を出力する可能性が非常に高くなります。これは、for ループが実行されるまでゴルーチンの実行が開始されない可能性が非常に高く、この時点で val の値がスライス内の最後の要素を指しているためです。
The val variable in the above loops is actually a single variable that takes on the value of each slice element. Because the closures are all only bound to that one variable, there is a very good chance that when you run this code you will see the last element printed for every iteration instead of each value in sequence, because the goroutines will probably not begin executing until after the loop.
解決策
上記のコードを記述する正しい方法は次のとおりです:
for val := range values { go func(val interface{}) { fmt.Println(val) }(val) }
ここで、val はパラメータとして goroutine に渡され、各 val は個別に計算されて保存されます。期待される結果を得るためにゴルーチンのスタックに追加します。
もう 1 つの方法は、ループ内で新しい変数を定義することです。ループ内で定義された変数はループの走査中に共有されないため、同じ効果が得られます。前述の問題を解決するには、ループに一時変数を追加し、後続のゴルーチン内のすべての i をこの一時変数に置き換えます。
以上がGolang の for ループと goroutine の詳細な例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。