问题描述
循环创建新协程,发现每次使用的循环变量都一样,都是最后一个
package main import ( "fmt" "time" ) func main() { type Student struct { Name string Age int } studentList := []*Student{ { Name: "张三", Age: 13, }, { Name: "李四", Age: 13, }, { Name: "王五", Age: 13, }, } for idx, stu := range studentList { go func() { fmt.Printf("%v: %v\n", idx, stu) }() } time.Sleep(3 * time.Second) }
输出
2: &{王五 13} 2: &{王五 13} 2: &{王五 13}
可以发现输出的下标都是 2,对象都是王五,这是因为 idx 和 stu 每次循环时都是同一个变量,每次用新值覆盖旧值,子协程内部的 idx 和 stu 也都是跟主线程里的idx 和 stu是同一个变量,如果主线程中idx和stu发生变化,子协程里的两个变量也会发生变化。循环速度太快,循环了三次才开始执行子协程代码,导致子协程执行时idx和 stu已经被覆盖成最新的了,所以三次打印出来的同样的值。
解决方案:
在每次循环时重新定义变量,这样每个协程使用的就是循环内部的变量,而不是外层循环时共享的变量
如果不好理解,可以改成下面这样,上面的只不过重新定义的两个变量名字跟外层循环里的变量名字一样
for idx, stu := range studentList { index, student := idx, stu go func() { fmt.Printf("%v: %v\n", index, student) }() }
输出
1: &{李四 13} 0: &{张三 13} 2: &{王五 13}
bye the way 1
下面这种企图通过给子协程传参的方式是无法解决上面的问题的,因为本质上传给协程的都是同两个变量,只不过变量的值变化了三次,等开始执行协程代码时,协程内部的遍历值已经被覆盖成最新修改的值了
输出
2: &{王五 13} 2: &{王五 13} 2: &{王五 13}
by the way 2
即使 range 循环时,每个元素的类型是值类型,不是引用类型,那他们也同样复用的是同个变量,就是每次循环都是修改了循环变量的值,而不是重新创建了一个循环变量。比如下面的代码,循环变量 element 是 int 类型,但是所有协程打印出来还是都是最后一次被赋的值 4。
func main() { arr := []int{1, 2, 3, 4} for _, element := range arr{ go func() { fmt.Println(element) }() } time.Sleep(time.Second * 3) }
输出结果
4 4 4 4
解决方案也是在开始使用循环变量之前,重新定义一个变量,新协程使用新变量而不是循环变量
输出结果
4 1 3 2