代码例子
下面代码的区别是直接调用循环变量,这里使用的就是这个变量的引用,而不是将参数的副本传递给协程执行
for task := range taskChan {
wg.Add(1)
go func() {
defer wg.Done()
task.Do() // 使用外部循环变量
}()
}
func DistributeTasks(taskChan <-chan *AddTask, resChan chan int) {
var wg sync.WaitGroup
for task := range taskChan {
wg.Add(1)
go func(t *AddTask) {
defer wg.Done()
t.Do()
}(task) // 注意要当作参数传入,而不是直接在 开启的协程 内部调用task,
}
wg.Wait()
close(resChan)
}
结论
- 可能导致的问题:
- 在 Go 语言中,当你使用 go 关键字启动一个 goroutine 时,它会在一个新的并发执行单元中运行。在原始代码中,将 task 作为参数传递给匿名函数,确保了每个 goroutine 操作的 task 是独立的,因为函数参数是按值传递的,这意味着在 go 语句执行时,会将 task 的副本传递给匿名函数。
- 如果不将 task 作为参数传递,而是直接在匿名协程 goroutine 中调用 task,由于 goroutine 可能会在 for 循环的下一次迭代开始后才开始执行,而 for 循环会不断更新 task 的值,这可能会导致 goroutine 看到的 task 值不是你期望的那个。
- 例如:goroutine执行时间很长,而程序中开启goroutine是一个很快的过程,开启完毕之后就执行下一次for循环了,循环变量task也就变了,之前开启的goroutine执行到task.Do() 的时候,已经是其他次循环的task变量
for task := range taskChan {
wg.Add(1)
go func() {
defer wg.Done()
//假如这里有比较久的耗时操作
task.Do() // 使用外部循环变量,这时候可能已经当时那一次循环的task了,因为开启协程这段代码一下子就执行完毕
}()
}