Fairly new to golang. I am a bit confused about go's variable scope. i have the following toy program
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
}
When Running this in the Go playground i got the following output:
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
Which raise a question in that when main_helper()
exit, why doesn't the local variable like count
and mu
go out of scope? Why are we still seeomg unfinished go routine updating the count variable correctly?
It is the result of "escape analysis". The compiler realizes that the variable count
escapes the function main_helper
because it is used in the goroutine, so that variable is allocated on heap instead of on stack. In general, you can return pointers to local variables, and because of escape analysis, compiler allocates that variable on the heap, like:
type X struct {
...
}
func NewX() *X {
return &X{}
}
This is a common constructor-like pattern to initialize structs. This code is equivalent to:
func NewX() *X {
return new(X)
}
In your program, the count:=0
declaration is equivalent to:
count:=new(int)
*count=0
and the goroutine keeps a pointer to count
. Same for finished
.