Go 中的for 循环闭包问题,是每个 Go 程序员几乎都踩过的坑,也是面试和实际开发中非常容易出错和引起 bug 的地方。这里我会通过原理、示例、修正方法、背后机制等角度详细为你讲解。
当你在 for 循环里写匿名函数(闭包),并且闭包用到了 for 的循环变量,容易出现“所有闭包用到的都是最后一轮的变量值”的问题。
典型错误写法:
for i := 0; i < 3; i++ {
go func() {
fmt.Println(i)
}()
}
time.Sleep(time.Second)
你期望输出 0 1 2,
实际可能输出:三次 3!
再举例:
list := []string{"a", "b", "c"}
for _, v := range list {
go func() {
fmt.Println(v)
}()
}
你期望看到:a、b、c
但实际上很大概率会看到:三次 c
(因为 v 最后变成了 c,所有匿名函数都打印这个地址的内容)
Go 中 for-range 里面的变量(如 v)内存地址是每轮复用的。
写法:每轮新建变量,其作用域在此次循环,仅此一份。
for _, v := range list {
val := v // 新分配一份,作用域只在这轮
go func() {
fmt.Println(val)
}()
}
写法:把变量作为闭包参数,立即传递。
for _, v := range list {
go func(x string) {
fmt.Println(x)
}(v) // 这里直接将本轮的v值作为参数传入
}
你的代码:
for _, trainplan := range trainplans {
tp := trainplan // 关键
tm.AddTask(tp.Crontabtask, func() {
// 在这里将tp用于闭包
})
}
这里 tp := trainplan,即每轮新建变量并赋值一份,其作用域独立,闭包拿到的是自己那一轮的拷贝,这是推荐用法!
Go官方FAQ:
“for statement reuse the same variable for each iteration, so each closure shares that variable. … Creating a new variable inside the loop, by assigning the loop variable to it, fixes the problem because a new variable is created each time through the loop…”
for 循环闭包抓变量,
新建变量解君愁。
变量传参进函数,
后患无忧代码优。
for _, element := range coll {
ele := element // 推荐
go func() {
fmt.Println(ele)
}()
}
// 或
for _, element := range coll {
go func(e string) {
fmt.Println(e)
}(element)
}
希望讲解清楚!如需更深入的编译器级机制或者更多Go惯用法,欢迎扩展追问!