Go遍历Map启动Goroutine时参数始终为最后迭代值的问题排查
Go遍历map启动Goroutine时参数始终为最后迭代值的修复方案
问题重现
原始代码
type Connection struct { Conn *somepackage.Conn // 示例中无需关注具体类型 Channel chan []byte } var connectionMap map[string]map[string]Connection ... for connID, conn := range connectionMap["someValue"] { fmt.Println(connID, conn) go func(c *Connection) { fmt.Println("inside fn", connID, c) }(&conn) }
运行输出
6c2989b7-4e52-48b1-aa6f-60c03c4fe78a {0xc0002dc9a0 0xc0000309c0} 283560e8-2ade-431e-92e2-55751b825c67 {0xc0004f0000 0xc0004981e0} inside fn 283560e8-2ade-431e-92e2-55751b825c67 &{0xc0004f0000 0xc0004981e0} inside fn 283560e8-2ade-431e-92e2-55751b825c67 &{0xc0004f0000 0xc0004981e0}
问题:遍历map启动Goroutine时,connID和传入的conn指针最终都只显示最后一次迭代的值。
原因分析
Go的for range循环会复用同一个变量存储空间,循环里的connID和conn是同一个变量,每次迭代仅更新它们的值。而Goroutine启动后不会立即执行,当它们真正运行时循环可能已完成,此时这两个变量已被更新为最后一次迭代的值,因此所有Goroutine都会读取到最后一组值。
另外,代码中虽将&conn作为参数传入Goroutine,但conn本身是循环复用的变量,每次传入的都是同一个内存地址,最终指向最后一次迭代的conn值。
修复方案
有两种常用修复方式,核心都是让每个Goroutine绑定当前迭代的独立值副本:
方式一:循环内创建局部变量保存当前迭代值
在每次迭代时,将当前的connID和conn赋值给局部变量,让Goroutine捕获这些独立的局部变量:
for connID, conn := range connectionMap["someValue"] { fmt.Println(connID, conn) // 创建局部变量保存当前迭代的独立值 currentID := connID currentConn := conn go func(c *Connection) { fmt.Println("inside fn", currentID, c) }(¤tConn) }
方式二:将迭代值作为参数传递给Goroutine
把connID也作为参数传入Goroutine,确保每个Goroutine拿到当前迭代的副本:
for connID, conn := range connectionMap["someValue"] { fmt.Println(connID, conn) // 将connID和conn都作为参数传入Goroutine go func(id string, c *Connection) { fmt.Println("inside fn", id, c) }(connID, &conn) }
验证效果
修复后运行,输出会变为:
6c2989b7-4e52-48b1-aa6f-60c03c4fe78a {0xc0002dc9a0 0xc0000309c0} 283560e8-2ade-431e-92e2-55751b825c67 {0xc0004f0000 0xc0004981e0} inside fn 6c2989b7-4e52-48b1-aa6f-60c03c4fe78a &{0xc0002dc9a0 0xc0000309c0} inside fn 283560e8-2ade-431e-92e2-55751b825c67 &{0xc0004f0000 0xc0004981e0}
每个Goroutine都会输出对应迭代的connID和conn值。
内容的提问来源于stack exchange,提问作者Shiva T. Kota
相关产品推荐
相关产品推荐

