關于Golang中for-loop與goroutine的問題詳解
背景
最近在學習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應該是很直觀的,我當時并沒有意識到有什么問題??墒窃谡{試過程中,一直在報 index out of bounds 錯誤。調試信息顯示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 變量上,因此極有可能上面的代碼的運行結果是所有 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 的棧中,從而得到預期的結果。
另一種方法是在循環(huán)內定義新的變量,由于在循環(huán)內定義的變量在循環(huán)遍歷的過程中是不共享的,因此也可以達到同樣的效果:
for i := range valslice { val := valslice[i] go func() { fmt.Println(val) }() }
對于文章開頭提到的那個問題,最簡單的解決方案就是在循環(huán)內加一個臨時變量,并將后面 goroutine 內的 i 都替換為這個臨時變量即可:
server := i
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關文章
go 原生http web 服務跨域restful api的寫法介紹
這篇文章主要介紹了go 原生http web 服務跨域restful api的寫法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04golang?gorm的關系關聯(lián)實現(xiàn)示例
這篇文章主要為大家介紹了golang?gorm的關系關聯(lián)實現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04