我们会经常性的使用如下片段

for k, v := range sli {
	go ...
}

来看下面的例子

func main() {
	sli := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
	wg := sync.WaitGroup{}
	for k, v := range sli {
		wg.Add(1)
		go func() {
			time.Sleep(time.Second)
			fmt.Println(k, v)
			wg.Done()
		}()
	}
	wg.Wait()
}

打印输出

8 9
8 9
8 9
8 9
8 9
8 9
8 9
8 9
8 9

结果和想象的不一样,主要原因出在协程这里,如果不使用协程,而是使用串行的方式,结果结合预期一致,比如

func main() {
	sli := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
	for k, v := range sli {
		func() {
			time.Sleep(time.Second)
			fmt.Println(k, v)
		}()
	}
}

打印输出

0 1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9

其中 k, v 是迭代变量,每次迭代都会给 k, v 赋值新的值,并且多个协程又同时调用了 k, v ,所以结果就串了,解决方式就是定义一个局部变量。

func main() {
	sli := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
	wg := sync.WaitGroup{}
	for k, v := range sli {
		wg.Add(1)

		k1 := k
		v1 := v
		
		go func() {
			time.Sleep(time.Second)
			fmt.Println(k1, v1)
			wg.Done()
		}()
	}
	wg.Wait()
}

打印输出

4 5
1 2
2 3
8 9
7 8
5 6
6 7
0 1
3 4
k1, v1 是局部变量k1 := k

或者通过传参的方式来固定下来

func main() {
	sli := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
	wg := sync.WaitGroup{}
	for k, v := range sli {
		wg.Add(1)

		go func(k, v interface{}) {
			time.Sleep(time.Second)
			fmt.Println(k, v)
			wg.Done()
		}(k, v)
	}
	wg.Wait()
}