我们知道goroutine函数会在一个不同于当前调用者线程的环境中运行;那么当调用者线程结束,或者调用者函数返回之后,goroutine函数还能不能使用调用者函数的局部变量呢。
下面代码例子描述这个问题
package main
import (
"log"
"time"
)
func foo() {
defer log.Println("defer foo()")
i := int(1);
j := int(2);
go func (p int) {
defer log.Println("defer goroute")
for k := 0; k < 5; k++ {
i += 10
log.Printf("p=%d,i=%d\n", p, i)
time.Sleep(2*time.Second)
}
log.Println("exit of goroute")
}(i)
for k := 0; k < 5; k++ {
i += 100
j += 100
time.Sleep(1*time.Second)
}
log.Println("exit of foo")
}
func main() {
defer log.Println("defer main")
foo()
for j := 0; j < 8; j++ {
time.Sleep(1*time.Second)
}
log.Println("exit of main")
}
首先main函数会调用foo函数,foo函数内部会起来一个匿名goroutine,在这个匿名routine里面会访问foo的局部变量i。
函数里面的time.Sleep()语句用来保证foo函数优先结束,然后是匿名goroutine,最后是main主函数。
运行结果如下:
2017/09/17 15:58:48 p=1,i=111
2017/09/17 15:58:50 p=1,i=321
2017/09/17 15:58:52 p=1,i=531
2017/09/17 15:58:53 exit of foo
2017/09/17 15:58:53 defer foo()
2017/09/17 15:58:54 p=1,i=541
2017/09/17 15:58:56 p=1,i=551
2017/09/17 15:58:58 exit of goroute
2017/09/17 15:58:58 defer goroute
2017/09/17 15:59:01 exit of main
2017/09/17 15:59:01 defer main
or
2017/09/17 15:59:05 p=1,i=111
2017/09/17 15:59:07 p=1,i=221
2017/09/17 15:59:09 p=1,i=431
2017/09/17 15:59:10 exit of foo
2017/09/17 15:59:10 defer foo()
2017/09/17 15:59:11 p=1,i=541
2017/09/17 15:59:13 p=1,i=551
2017/09/17 15:59:15 exit of goroute
2017/09/17 15:59:15 defer goroute
2017/09/17 15:59:18 exit of main
2017/09/17 15:59:18 defer main
可以看到匿名goroutine函数正常的使用了foo函数中定义的局部变量i,尽管函数foo已经退出,并且foo的defer部分已经执行了,那为什么局部变量i还能继续需用呢?答案是我们在前面文章也分析过了,go语言编译器会根据变量的逃逸分析,自动做出选择把一个变量分配的堆还是栈,在这个例子中很显然局部变量i被分配在了堆中。
我们看一下foo的汇编代码片段;为了便于区分,foo同时定义了两个局部变量i和j,i被goroutine使用了,而j没有:
0x00d6 00214 (/.../src/main/main.go:10) LEAQ type.int(SB), AX
0x00dd 00221 (/.../src/main/main.go:11) MOVQ AX, (SP)
0x00e1 00225 (/.../src/main/main.go:11) PCDATA $0, $1
0x00e1 00225 (/.../src/main/main.go:11) CALL runtime.newobject(SB)
0x00e6 00230 (/.../main/main.go:11) MOVQ 8(SP), AX
0x00eb 00235 (/.../src/main/main.go:11) MOVQ AX, "".&i+104(SP)
0x00f0 00240 (/.../src/main/main.go:11) MOVQ $1, (AX)
0x00f7 00247 (/.../src/main/main.go:12) MOVQ $2, "".j+48(SP)
可以看出i被分配到了堆里面通过runtime.newobject出一个对象,而j直接分配在了栈上;所以不管foo是否已经退出,匿名goroutine都可以在堆中访问到i的值。
网友评论