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

Issue

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?

Solution

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.

Answered By – Burak Serdar

Answer Checked By – Katrina (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.