Search code examples
multithreadinggoconcurrencygoroutine

How does Golang share variables between goroutines?


I'm learning Go and trying to understand its concurrency features.

I have the following program.

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 5; i++ {
        wg.Add(1)

        x := i

        go func() {
            defer wg.Done()
            fmt.Println(x)
        }()

    }

    wg.Wait()
    fmt.Println("Done")
}

When executed I got:

4
0
1
3
2

It's just what I want. However, if I make slight modification to it:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 5; i++ {
        wg.Add(1)

        go func() {
            defer wg.Done()
            fmt.Println(i)
        }()

    }

    wg.Wait()
    fmt.Println("Done")
}

What I got will be:

5
5
5
5
5

I don't quite understand the difference. Can anyone help to explain what happened here and how Go runtime execute this code?


Solution

  • You have new variable on each run of x := i,
    This code shows difference well, by printing the address of x inside goroutine:
    The Go Playground:

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func main() {
        var wg sync.WaitGroup
        for i := 0; i < 5; i++ {
            wg.Add(1)
            x := i
            go func() {
                defer wg.Done()
                fmt.Println(&x)
            }()
        }
        wg.Wait()
        fmt.Println("Done")
    }
    

    output:

    0xc0420301e0
    0xc042030200
    0xc0420301e8
    0xc0420301f0
    0xc0420301f8
    Done
    

    And build your second example with go build -race and run it:
    You will see: WARNING: DATA RACE


    And this will be fine The Go Playground:

    //go build -race
    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func main() {
        var wg sync.WaitGroup
        for i := 0; i < 5; i++ {
            wg.Add(1)
            go func(i int) {
                defer wg.Done()
                fmt.Println(i)
            }(i)
        }
        wg.Wait()
        fmt.Println("Done")
    }
    

    output:

    0
    4
    1
    2
    3
    Done