函数退出后,例程如何从调用函数访问局部变量?

How does go routine access local variable from calling function after function exited?

刚接触 golang。我对 go 的变量范围有点困惑。我有以下玩具程序

package main

import "sync"
import "time"
import "fmt"
import "math/rand"

func main() {
    go main_helper()
    time.Sleep(time.Duration(1000000) * time.Millisecond)

}
func main_helper() {
    rand.Seed(time.Now().UnixNano())

    count := 0
    finished := 0
    var mu sync.Mutex
    cond := sync.NewCond(&mu)

    for i := 0; i < 10; i++ {
        go func(i int) {
            vote := requestVote(i)
            mu.Lock()
            defer mu.Unlock()
            if vote {
                count++
            }
            fmt.Printf("cur_count: %d\n", count)
            finished++
            cond.Broadcast()
        }(i)
    }

    mu.Lock()
    for count < 5 {
        cond.Wait()
    }
    if count >= 5 {
        println("received 5+ votes!")
    } else {
        println("lost")
    }
    mu.Unlock()
    fmt.Printf("Exited main loop\n")

}

func requestVote(i int) bool {
    if i > 6 {
        time.Sleep(time.Duration(1000) * time.Millisecond)
    } else {
        time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
    }
    fmt.Printf("go routine: %d requested vote\n", i)
    return true
}

当 运行 在 Go playground 中执行此操作时,我得到以下输出:

go routine: 0 requested vote
cur_count: 1
go routine: 6 requested vote
cur_count: 2
go routine: 1 requested vote
cur_count: 3
go routine: 4 requested vote
cur_count: 4
go routine: 2 requested vote
cur_count: 5
received 5+ votes!
Exited main loop
go routine: 3 requested vote
cur_count: 6
go routine: 5 requested vote
cur_count: 7
go routine: 7 requested vote
cur_count: 8
go routine: 8 requested vote
cur_count: 9
go routine: 9 requested vote
cur_count: 10

这就提出了一个问题,当main_helper()退出时,为什么像countmu这样的局部变量没有超出作用域?为什么我们仍然看到未完成的例程正确更新计数变量?

是“逃逸分析”的结果。编译器意识到变量 count 转义了函数 main_helper 因为它在 goroutine 中使用,所以变量分配在堆上而不是堆栈上。通常,您可以 return 指向局部变量的指针,并且由于逃逸分析,编译器会在堆上分配该变量,例如:

type X struct {
   ...
}

func NewX() *X {
  return &X{}
}

这是初始化结构的常见 constructor-like 模式。此代码等效于:

func NewX() *X {
  return new(X)
}

在您的程序中,count:=0 声明等同于:

count:=new(int)
*count=0

并且 goroutine 保留指向 count 的指针。 finished.

相同

How does go routine access local variable from calling function after function exited?

任何符合 Go 语言规范的编译器都可以使用其作者选择实现的任何技术,例如将所有变量放在堆上,根本不释放内存,使用引用计数或只将一些变量放在堆栈上。