关于Golang中for-loop与goroutine的问题详解
背景
最近在学习MIT的分布式课程6.824的过程中,使用Go实现Raft协议时遇到了一些问题。分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。
参见如下代码:
fori:=0;ilen(rf.peers)/2{ rf.winElectionCh<-true } } }() }
其中,peers切片的长度为3,因此最高下标为2,在非并行编程中代码中的for-loop应该是很直观的,我当时并没有意识到有什么问题。可是在调试过程中,一直在报indexoutofbounds错误。调试信息显示i的值为3,当时就一直想不明白循环条件明明是i<2,怎么会变成3呢。
分析
虽然不明白发生了什么,但知道应该是循环中引入的goroutine导致的。经过Google,发现Go的wiki中就有一个页面CommonMistake-Usinggoroutinesonloopiteratorvariables专门提到了这个问题,看来真的是很common啊,笑哭~
初学者经常会使用如下代码来并行处理数据:
forval:=rangevalues{ goval.MyMethod() }
或者使用闭包(closure):
forval:=rangevalues{ gofunc(){ fmt.Println(val) }() }
这里的问题在于val实际上是一个遍历了切片中所有数据的单一变量。由于闭包只是绑定到这个val变量上,因此极有可能上面的代码的运行结果是所有goroutine都输出了切片的最后一个元素。这是因为很有可能当for-loop执行完之后goroutine才开始执行,这个时候val的值指向切片中最后一个元素。
Thevalvariableintheaboveloopsisactuallyasinglevariablethattakesonthevalueofeachsliceelement.Becausetheclosuresareallonlyboundtothatonevariable,thereisaverygoodchancethatwhenyourunthiscodeyouwillseethelastelementprintedforeveryiterationinsteadofeachvalueinsequence,becausethegoroutineswillprobablynotbeginexecutinguntilaftertheloop.
解决方法
以上代码正确的写法为:
forval:=rangevalues{ gofunc(valinterface{}){ fmt.Println(val) }(val) }
在这里将val作为一个参数传入goroutine中,每个val都会被独立计算并保存到goroutine的栈中,从而得到预期的结果。
另一种方法是在循环内定义新的变量,由于在循环内定义的变量在循环遍历的过程中是不共享的,因此也可以达到同样的效果:
fori:=rangevalslice{ val:=valslice[i] gofunc(){ fmt.Println(val) }() }
对于文章开头提到的那个问题,最简单的解决方案就是在循环内加一个临时变量,并将后面goroutine内的i都替换为这个临时变量即可:
server:=i
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。