背景
最近在學習MIT的分布式課程6.824的過程中,使用Go實現(xiàn)Raft協(xié)議時遇到了一些問題。分享出來供大家參考學習,下面話不多說了,來一起看看詳細的介紹吧。
參見如下代碼:
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
}
}
}()
}
其中,peers切片的長度為3,因此最高下標為2,在非并行編程中代碼中的for-loop應該是很直觀的,我當時并沒有意識到有什么問題??墒窃谡{(diào)試過程中,一直在報 index out of bounds 錯誤。調(diào)試信息顯示i的值為3,當時就一直想不明白循環(huán)條件明明是 i 2,怎么會變成3呢。
分析
雖然不明白發(fā)生了什么,但知道應該是循環(huán)中引入的 goroutine 導致的。經(jīng)過Google,發(fā)現(xiàn)Go的wiki中就有一個頁面 Common Mistake - Using goroutines on loop iterator variables 專門提到了這個問題,看來真的是很 common 啊,笑哭~
初學者經(jīng)常會使用如下代碼來并行處理數(shù)據(jù):
for val := range values {
go val.MyMethod()
}
或者使用閉包(closure):
for val := range values {
go func() {
fmt.Println(val)
}()
}
這里的問題在于 val 實際上是一個遍歷了切片中所有數(shù)據(jù)的單一變量。由于閉包只是綁定到這個 val 變量上,因此極有可能上面的代碼的運行結(jié)果是所有 goroutine 都輸出了切片的最后一個元素。這是因為很有可能當 for-loop 執(zhí)行完之后 goroutine 才開始執(zhí)行,這個時候 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 作為一個參數(shù)傳入 goroutine 中,每個 val 都會被獨立計算并保存到 goroutine 的棧中,從而得到預期的結(jié)果。
另一種方法是在循環(huán)內(nèi)定義新的變量,由于在循環(huán)內(nèi)定義的變量在循環(huán)遍歷的過程中是不共享的,因此也可以達到同樣的效果:
for i := range valslice {
val := valslice[i]
go func() {
fmt.Println(val)
}()
}
對于文章開頭提到的那個問題,最簡單的解決方案就是在循環(huán)內(nèi)加一個臨時變量,并將后面 goroutine 內(nèi)的 i 都替換為這個臨時變量即可:
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
您可能感興趣的文章:- 解決Golang中goroutine執(zhí)行速度的問題
- golang goroutine順序輸出方式
- golang gin 框架 異步同步 goroutine 并發(fā)操作
- GOLANG使用Context管理關聯(lián)goroutine的方法
- Golang 探索對Goroutine的控制方法(詳解)
- Golang 語言控制并發(fā) Goroutine的方法