golang for 循环中使用协程的问题

两个例子

package main

import (
	"fmt"
	"time"
)

func Process1(tasks []string) {
	for _, task := range tasks {
		// 启动协程并发处理任务
		go func() {
			fmt.Printf("Worker start process task: %s\n", task)
		}()
	}
}

func main() {

	tasks := []string{"1", "2", "3", "4", "5"}
	Process1(tasks)
	time.Sleep(2 * time.Second)
}
结果:
第一次运行
Worker start process task: 3
Worker start process task: 4
Worker start process task: 4
Worker start process task: 5
Worker start process task: 5
第二次运行
Worker start process task: 2
Worker start process task: 5
Worker start process task: 5
Worker start process task: 5
Worker start process task: 5

package main

import (
	"fmt"
	"time"
)

func Process1(tasks []string) {
	for _, task := range tasks {
		// 启动协程并发处理任务
		go func() {
			fmt.Printf("Worker start process task: %s\n", task)
		}()
	}
}

func Process2(tasks []string) {
	for _, task := range tasks {
		// 启动协程并发处理任务
		go func(t string) {
			fmt.Printf("Worker start process task: %s\n", t)
		}(task)
	}
}
func main() {
	tasks := []string{"1", "2", "3", "4", "5"}
	Process2(tasks)
	time.Sleep(2 * time.Second)
}
结果
第一次运行
Worker start process task: 5
Worker start process task: 4
Worker start process task: 2
Worker start process task: 3
Worker start process task: 1
第二次运行
Worker start process task: 2
Worker start process task: 5
Worker start process task: 4
Worker start process task: 1
Worker start process task: 3

上述问题,有个共同点就是都引用了循环变量。即在for index, value := range xxx语句中,
indexvalue便是循环变量。不同点是循环变量的使用方式,有的是直接在协程中引用(题目一),有的作为参数传递(题目二)。

循环变量是易变的

首先,循环变量实际上只是一个普通的变量。

语句for index, value := range xxx中,每次循环index和value都会被重新赋值(并非生成新的变量)。

如果循环体中会启动协程(并且协程会使用循环变量),就需要格外注意了,因为很可能循环结束后协程才开始执行,
此时,所有协程使用的循环变量有可能已被改写。(是否会改写取决于引用循环变量的方式)

循环变量需要绑定

在题目一中,协程函数体中引用了循环变量task,协程从被创建到被调度执行期间循环变量极有可能被改写,所以会出现两次结果相差较大,比如第一个协程启动for range变量正好循环到3,for属于主协程的一部分。go func是子协程,主子分开看。这种情况下,其实for range里面的循环变量没有跟子协程绑定,称之为变量没有绑定。所以,题目一打印结果是混乱的。很有可能(随机)所有协程执行的task都是列表中的最后一个task,也可能不是。

在题目二中,协程函数体中并没有直接引用循环变量task,而是使用的参数与协程进行了绑定。而在创建协程时,循环变量task
作为函数参数传递给了协程。参数传递的过程实际上也生成了新的变量,也即间接完成了绑定。
所以,题目二实际上是没有问题的。就是实际参数顺序是按照for range产生的变量顺序绑定给子协程的。

ps:

简单点来说

  • 如果循环体没有并发出现,则引用循环变量一般不会出现问题;
  • 如果循环体有并发,则根据引用循环变量的位置不同而有所区别
    • 通过参数完成绑定,则一般没有问题;
    • 函数体中引用,则需要显式地绑定

参照 topgoer的文章。

猜你喜欢

转载自blog.csdn.net/lengyue1084/article/details/108123166
今日推荐