陈哈哈的博客

Go语言重点笔记-幽灵变量陷阱

2018-05-18

幽灵变量并非GoLang特有的问题。

在For循环中,迭代变量在初始时创建,后续被重复使用。如果在其内部循环中使用go func(){}闭包,并直接访问其循环变量,会出现循环变量固定为最后的值的问题。如果没有理解产生此问题的根源,在运行结果表现上看,循环变量像幽灵一般不可理解。

详见示例代码:

我们期望的输出结果是:

[0] -> yoojia
[1] -> github.com
[2] -> yoojia.xyz

而事实上,上例子输出结果却是:

[2] -> yoojia.xyz
[2] -> yoojia.xyz
[2] -> yoojia.xyz

究其原因,在于func()go关键字修饰,For循环比内部goroutine执行要快,所以最终当go func()被调度时,for循环已经执行完毕(或迭代很多),内部循环变量已经被多次更新。在go func()协程被执行时,循环变量已不是当前启动瞬间的值。如果改为func()()则没有这种问题。

Java例子:

如上所示,Java编译器自动检查 Thread 内部类的finalIdx变量范围,强制要求内部类访问外部局部变量时,必须为Final值或引用。Java使用Final来修饰内部类所访问的对象,通过拷贝的方式来确保内部类所访问的值是循环当时捕获的值。

Go的解决方案

在Go中要避免这样的陷阱,有两种方法:

  1. 像Java一样,在循环内部,重新定义一个变量,改让go func()引用此变量;
  2. 修改go func()的闭包函数签名go func(int, string),将循环变量作为参数,借助于函数传值参数的拷贝特性来实现捕获循环当时的值。