美文网首页Golang语言社区go学习Go语言用例
goroutine访问宿主函数局部变量

goroutine访问宿主函数局部变量

作者: CodingCode | 来源:发表于2017-09-17 16:10 被阅读24次

    我们知道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的值。

    相关文章

      网友评论

        本文标题:goroutine访问宿主函数局部变量

        本文链接:https://www.haomeiwen.com/subject/indjsxtx.html