问题描述
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游乐场中运行此命令时,我得到以下输出:
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()
时,为什么count
和mu
这样的局部变量不会超出范围?为什么我们仍未完成未完成的go例程,正确地更新了count变量?
解决方法
是“转义分析”的结果。编译器意识到变量count
逃逸了函数main_helper
,因为它在goroutine中使用,因此该变量分配在堆上而不是堆栈上。通常,您可以返回指向局部变量的指针,并且由于进行转义分析,编译器会在堆上分配该变量,例如:
type X struct {
...
}
func NewX() *X {
return &X{}
}
这是一个类似于构造函数的通用模式,用于初始化结构。此代码等效于:
func NewX() *X {
return new(X)
}
在您的程序中,count:=0
声明等效于:
count:=new(int)
*count=0
,goroutine保持指向count
的指针。与finished
相同。
函数退出后,例程如何从调用函数访问局部变量?
任何符合Go语言规范的编译器都可以使用其作者选择实施的任何技术,例如将所有变量放在堆上,而不释放内存,使用引用计数或仅将某些变量放在堆栈上。