Why the last goroutine starts first in a loop with two goroutines?

Issue

I have been doing some tests with goroutines and I noticed a weird behaviour (for me) using this code:

Playground: https://play.golang.org/p/Py4oqGqkYKm

package main

import (
    "fmt"
    "sync"
)
var (
    mu sync.Mutex
    wg sync.WaitGroup
)

func main() {
    var x int
    for i := 0; i < 10; i++ {
        wg.Add(2)
        go func() {
            mu.Lock()
            x = i
            mu.Unlock()
            wg.Done()
        }()
        go func() {
            mu.Lock()
            fmt.Print("x: ", x, " \n")
            mu.Unlock()
            wg.Done()
        }()
        wg.Wait()
    }
}

I have expected as output something such as:

x: 0 
x: 1 
x: 2 
x: 3 
x: 4 
x: 5 
x: 6 
x: 7 
x: 8 
x: 9 

But I have received:

x: 0 
x: 0 
x: 1 
x: 2 
x: 3 
x: 4 
x: 5 
x: 6 
x: 7 
x: 8 

It looks like that the second goroutines is called first (like LIFO). Thinking that, I have tried to invert the goroutines and I received the answer that I have expected:

Playground: https://play.golang.org/p/BC1r3NK6RBm

package main

import (
    "fmt"
    "sync"
)

var (
    mu sync.Mutex
    wg sync.WaitGroup
)

func main() {
    var x int
    for i := 0; i < 10; i++ {
        wg.Add(2)
        go func() {
            mu.Lock()
            fmt.Print("x: ", x, " \n")
            mu.Unlock()
            wg.Done()
        }()
        go func() {
            mu.Lock()
            x = i
            mu.Unlock()
            wg.Done()
        }()
        wg.Wait()
    }
}

Output:

x: 0 
x: 1 
x: 2 
x: 3 
x: 4 
x: 5 
x: 6 
x: 7 
x: 8 
x: 9 

Could anyone help me to understand this behavior?

Go version:
go version go1.16.2 linux/amd64

Solution

The Go Language does not specify the order that goroutines execute outside of explicit synchronization using channels, mutexes, wait groups and so on. The specification allows for both outputs from both programs.

You are observing the order that the mutex is acquired, not the order that the goroutines start. It could be that the goroutines start in your expected order, but the unexpected goroutine calls Lock() first.

The only ordering ensured by the program is through the wait group: Each pair of goroutines will complete before the next pair is started.

Answered By – user13631587

Answer Checked By – Clifford M. (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.