关于Golang中for-loop与goroutine的问题详解
作者:寇池滨 发布时间:2024-04-29 13:03:16
背景
最近在学习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
}
}
}()
}
其中,peers切片的长度为3,因此最高下标为2,在非并行编程中代码中的for-loop应该是很直观的,我当时并没有意识到有什么问题。可是在调试过程中,一直在报 index out of bounds 错误。调试信息显示i的值为3,当时就一直想不明白循环条件明明是 i < 2,怎么会变成3呢。
分析
虽然不明白发生了什么,但知道应该是循环中引入的 goroutine 导致的。经过Google,发现Go的wiki中就有一个页面 Common Mistake - Using goroutines on loop iterator variables 专门提到了这个问题,看来真的是很 common 啊,笑哭~
初学者经常会使用如下代码来并行处理数据:
for val := range values {
go val.MyMethod()
}
或者使用闭包(closure):
for val := range values {
go func() {
fmt.Println(val)
}()
}
这里的问题在于 val 实际上是一个遍历了切片中所有数据的单一变量。由于闭包只是绑定到这个 val 变量上,因此极有可能上面的代码的运行结果是所有 goroutine 都输出了切片的最后一个元素。这是因为很有可能当 for-loop 执行完之后 goroutine 才开始执行,这个时候 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 都会被独立计算并保存到 goroutine 的栈中,从而得到预期的结果。
另一种方法是在循环内定义新的变量,由于在循环内定义的变量在循环遍历的过程中是不共享的,因此也可以达到同样的效果:
for i := range valslice {
val := valslice[i]
go func() {
fmt.Println(val)
}()
}
对于文章开头提到的那个问题,最简单的解决方案就是在循环内加一个临时变量,并将后面 goroutine 内的 i 都替换为这个临时变量即可:
server := i
来源:https://segmentfault.com/a/1190000010884717
猜你喜欢
- open(filename,mode,buffer) 其中第一个参数是要打开的文件的文件名,必选;第二个是打开方式,可选;第三个为缓冲区,可
- let和const声明的变量只在代码块内有效{let a = 10;var b = 1;}a // ReferenceError: a is
- 1. 循环require在JavaScript中,模块之间可能出现相互引用的情况,例如现在有三个模块,他们之间的相互引用关系如下,大致的引用
- 两张表 组织架构表(Organise) 和 工资发放历史记录表 (WagePerMonthHis) 两张表通过 Organise.Item_
- 这篇文章主要介绍了python函数不定长参数使用方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的
- 1. 搭建项目配置环境和创建表创建一个ttsx的项目django-admin startproject ttsx在ttsx下的__init_
- 针对这种情况,人工智能自动SQL优化工具应运而生。现在我就向大家介绍这样一款工具:SQLTuning for SQL Server。1. S
- 在日常的测试工作中,我们的测试用例一般都是保存在Excel文件中,当然也有一些公司会使用Xmind来编写测试用例,那么为什么我们在这里只是讲
- 这个可以说属性选择符的JS版,用来遴选元素是适合不过。在开始之前,我们复习一下CSS2的属性选择符,JQuery高手可以跳过。属性选择符:名
- 一、安装pip install pymongo二、连接数据库import pymongo# 方式一client = pymongo.Mong
- 夹角余弦(Cosine)也可以叫余弦相似度。 几何中夹角余弦可用来衡量两个向量方向的差异,机器学习中借用这一概念来衡量样本向量之间的差异。(
- 目录一、前端控制1、在router.js文件(把静态路由和动态路由分别写在router.js)2、store/permission.js(在
- 1、基本原理访问网站扫码登录页,网站给浏览器返回一个二维码和一个唯一标志KEY浏览器开启定时轮询服务器,确认KEY对应的扫码结果用户使用ap
- 数据库操作当中,当数据库对象列表不只有一个普通的元素——objectname时,你将要使用objectowner.objectname来引用
- getattr`getattr`函数属于内建函数,可以通过函数名称获取value = obj.attributevalue = getatt
- 0x00 marshalmarshal使用的是与Python语言相关但与机器无关的二进制来读写Python对象的。这种二进制的格式也跟Pyt
- 我需要查询从现在算起五天前的日期。按照商业习惯,这五天应该不包含星期六和星期天。专家回答:对于许多跟商业日期有关的情况,最好的解决方案是使用
- 调用sklearn的model_selection时,发现sklearn中没有model_selection的模块。经过检查,发现anaco
- FCKeditor至今已经到了2.3.1版本了,对于国内的WEB开发者来说,也基本上都已经“闻风知多少”了,很多人将其融放到自己的项目中,更
- 本文实例讲述了Python实现的求解最大公约数算法。分享给大家供大家参考,具体如下:使用Python求解两个数的最大公约数的时候用到了前面介