函数退出后,例程如何从调用函数访问局部变量?
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()
退出时,为什么像count
和mu
这样的局部变量没有超出作用域?为什么我们仍然看到未完成的例程正确更新计数变量?
是“逃逸分析”的结果。编译器意识到变量 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 语言规范的编译器都可以使用其作者选择实现的任何技术,例如将所有变量放在堆上,根本不释放内存,使用引用计数或只将一些变量放在堆栈上。
刚接触 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()
退出时,为什么像count
和mu
这样的局部变量没有超出作用域?为什么我们仍然看到未完成的例程正确更新计数变量?
是“逃逸分析”的结果。编译器意识到变量 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 语言规范的编译器都可以使用其作者选择实现的任何技术,例如将所有变量放在堆上,根本不释放内存,使用引用计数或只将一些变量放在堆栈上。