sof
sof

Reputation: 9649

Why the shared variable between Go routines won't updated

Why does the code snippet below print out 0? However, i is correctly updated to 1 without the for loop.

package main

import (
    "fmt"
    "time"
)

func main() {

    var i uint64
    go func() {
        for {
            i++
        }
    }()

    time.Sleep(time.Second)
    fmt.Println(i)
}

Solution 1

package main

import (
        "fmt"
        "runtime"
        "time"
)

func main() {

        runtime.GOMAXPROCS(1)

        var i uint64
        ch := make(chan bool)
        go func() {
                for {
                        select {
                        case <-ch:
                                return
                        default:
                                i++
                        }
                }
        }()

        time.Sleep(time.Second)
        ch <- true
        fmt.Println(i)
}

Solution 2

package main

import (
        "fmt"
        "runtime"
        "sync"
        "time"
)

func main() {

        runtime.GOMAXPROCS(1)

        var i uint64
        var mx sync.Mutex
        go func() {
                for {
                        mx.Lock()
                        i++
                        mx.Unlock()
                }
        }()

        time.Sleep(time.Second)
        mx.Lock()
        fmt.Println(i)
        mx.Unlock()
}

Solution 3

package main

import (
        "fmt"
        "runtime"
        "sync/atomic"
        "time"
)

func main() {

        runtime.GOMAXPROCS(1)

        var i uint64
        go func() {
                for {
                        atomic.AddUint64(&i, 1)
                        runtime.Gosched()
                }
        }()

        time.Sleep(time.Second)
        fmt.Println(atomic.LoadUint64(&i))
}

Upvotes: 1

Views: 2520

Answers (4)

k1m190r
k1m190r

Reputation: 1313

Necessary reading for understanding - The Go Memory Model

The Go memory model specifies the conditions under which reads of a variable in one goroutine can be guaranteed to observe values produced by writes to the same variable in a different goroutine.

Whenever you have 2 or more concurrent goroutines accessing same memory and one of them modifying memory (like i++ in your example) you need to have some sort of synchronization: mutex or channel.

Something like this would be smallest modification necessary to your code:

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {

    var (
        mu sync.Mutex
        i  uint64
    )

    go func() {
        for {
            mu.Lock()
            i++
            mu.Unlock()
        }
    }()

    time.Sleep(time.Second)

    mu.Lock()
    fmt.Println(i)
    mu.Unlock()
}

Oh and official Advice:

If you must read the rest of this document to understand the behavior of your program, you are being too clever. Don't be clever.

:) have fun

Upvotes: 2

peterSO
peterSO

Reputation: 166578

You have a race condition. The results are undefined.

package main

import (
    "fmt"
    "time"
)

func main() {

    var i uint64
    go func() {
        for {
            i++
        }
    }()

    time.Sleep(time.Second)
    fmt.Println(i)
}

Output:

$ go run -race racer.go
==================
WARNING: DATA RACE
Read at 0x00c0000a2010 by main goroutine:
  main.main()
      /home/peter/src/racer.go:18 +0x95

Previous write at 0x00c0000a2010 by goroutine 6:
  main.main.func1()
      /home/peter/src/racer.go:13 +0x4e

Goroutine 6 (running) created at:
  main.main()
      /home/peter/src/racer.go:11 +0x7a
==================
66259553
Found 1 data race(s)
exit status 66
$ 

Reference: Introducing the Go Race Detector

Upvotes: 2

see sharper
see sharper

Reputation: 12035

Running this in the go playground, it times out, which slightly surprises me because, as Tarion says, the infinite for loop should be terminated upon exit from main. It seems that the go playground waits for all goroutines to complete before exiting. I imagine the reason you see 0 is to do with bad multithreading here. The for loop is continually updating i at the same time as i is read by main, and the results are unpredictable. I'm not sure what you are trying to demonstrate, but you would need a mutex here or a channel to read and update i safely on different threads.

Upvotes: 1

Tarion
Tarion

Reputation: 17134

It's not "correctly" updated without the for loop, it's just randomly updated before you print it.

In go playgorund this ends with:

process took too long 
Program exited.

See: https://play.golang.org/p/qKd-CdLt3uK

When starting the go routine the scheduler will decide when to switch the context between the execution of the go routine and the main function. In addition the go-routine is stopped when the main function ends.

What you want to do is some Synchronisation between your go routine and your main loop, since both are accessing a shared variable i.

Best practice here would be to use channels, sync.Mutex or sync.WaitGroup.

You can add some delay to your for loop, to give up some CPU for the main function:

go func() {
    for {
        i++
        time.Sleep(1 * time.Millisecond)
    }
}()

This will pint some value around 1000, but it's still not reliable.

Upvotes: 0

Related Questions