關(guān)于Golang中for-loop與goroutine的問(wèn)題詳解
背景
最近在學(xué)習(xí)MIT的分布式課程6.824的過(guò)程中,使用Go實(shí)現(xiàn)Raft協(xié)議時(shí)遇到了一些問(wèn)題。分享出來(lái)供大家參考學(xué)習(xí),下面話不多說(shuō)了,來(lái)一起看看詳細(xì)的介紹吧。
參見(jiàn)如下代碼:
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切片的長(zhǎng)度為3,因此最高下標(biāo)為2,在非并行編程中代碼中的for-loop應(yīng)該是很直觀的,我當(dāng)時(shí)并沒(méi)有意識(shí)到有什么問(wèn)題??墒窃谡{(diào)試過(guò)程中,一直在報(bào) index out of bounds 錯(cuò)誤。調(diào)試信息顯示i的值為3,當(dāng)時(shí)就一直想不明白循環(huán)條件明明是 i < 2,怎么會(huì)變成3呢。
分析
雖然不明白發(fā)生了什么,但知道應(yīng)該是循環(huán)中引入的 goroutine 導(dǎo)致的。經(jīng)過(guò)Google,發(fā)現(xiàn)Go的wiki中就有一個(gè)頁(yè)面 Common Mistake - Using goroutines on loop iterator variables 專門(mén)提到了這個(gè)問(wèn)題,看來(lái)真的是很 common 啊,笑哭~
初學(xué)者經(jīng)常會(huì)使用如下代碼來(lái)并行處理數(shù)據(jù):
for val := range values {
go val.MyMethod()
}
或者使用閉包(closure):
for val := range values {
go func() {
fmt.Println(val)
}()
}
這里的問(wèn)題在于 val 實(shí)際上是一個(gè)遍歷了切片中所有數(shù)據(jù)的單一變量。由于閉包只是綁定到這個(gè) val 變量上,因此極有可能上面的代碼的運(yùn)行結(jié)果是所有 goroutine 都輸出了切片的最后一個(gè)元素。這是因?yàn)楹苡锌赡墚?dāng) for-loop 執(zhí)行完之后 goroutine 才開(kāi)始執(zhí)行,這個(gè)時(shí)候 val 的值指向切片中最后一個(gè)元素。
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.
解決方法
以上代碼正確的寫(xiě)法為:
for val := range values {
go func(val interface{}) {
fmt.Println(val)
}(val)
}
在這里將 val 作為一個(gè)參數(shù)傳入 goroutine 中,每個(gè) val 都會(huì)被獨(dú)立計(jì)算并保存到 goroutine 的棧中,從而得到預(yù)期的結(jié)果。
另一種方法是在循環(huán)內(nèi)定義新的變量,由于在循環(huán)內(nèi)定義的變量在循環(huán)遍歷的過(guò)程中是不共享的,因此也可以達(dá)到同樣的效果:
for i := range valslice {
val := valslice[i]
go func() {
fmt.Println(val)
}()
}
對(duì)于文章開(kāi)頭提到的那個(gè)問(wèn)題,最簡(jiǎn)單的解決方案就是在循環(huán)內(nèi)加一個(gè)臨時(shí)變量,并將后面 goroutine 內(nèi)的 i 都替換為這個(gè)臨時(shí)變量即可:
server := i
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
一文帶你了解Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)strings的常用函數(shù)和方法
strings?庫(kù)包含了許多高效的字符串常用操作的函數(shù)和方法,巧用這些函數(shù)與方法,能極大的提高我們程序的性能。本文就來(lái)和大家分享一下Go標(biāo)準(zhǔn)庫(kù)strings的常用函數(shù)和方法,希望對(duì)大家有所幫助2022-11-11
一文幫你搞懂Go面試中常問(wèn)的channel問(wèn)題
channel是Golang面試時(shí)經(jīng)常會(huì)問(wèn)到的問(wèn)題,所以這篇文章為大家整理了channel常考的一些問(wèn)題以及回答,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-06-06
Golang?rabbitMQ生產(chǎn)者消費(fèi)者實(shí)現(xiàn)示例
這篇文章主要為大家介紹了Golang?rabbitMQ生產(chǎn)者消費(fèi)者實(shí)現(xiàn)的示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04
基于golang中container/list包的用法說(shuō)明
這篇文章主要介紹了基于golang中container/list包的用法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04
golang實(shí)現(xiàn)多協(xié)程下載文件(支持?jǐn)帱c(diǎn)續(xù)傳)
本文主要介紹了golang實(shí)現(xiàn)多協(xié)程下載文件,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
Go如何實(shí)現(xiàn)HTTP請(qǐng)求限流示例
本篇文章主要介紹了Go如何實(shí)現(xiàn)HTTP請(qǐng)求限流示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04

