York
York

Reputation: 13

The variable in Goroutine not changed as expected

The codes are simple as below:

package main

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

var count = uint64(0)

//var l sync.Mutex

func add() {
        for {
                //              l.Lock()
                //              fmt.Println("Start ++")
                count++
                //              l.Unlock()
        }
}

func main() {
        go add()
        time.Sleep(1 * time.Second)
        fmt.Println("Count =", count)
}

Cases:

  1. Running the code without changing, u will get "Count = 0". Not expected??
  2. Only uncomment line 16 "fmt.Println("Start ++")"; u will get output with lots of "Start ++" and some value with Count like "Count = 11111". Expected??
  3. Only uncomment line 11 "var l sync.Mutex", line 15 "l.Lock()" and line 18 "l.Unlock()" and keep line 16 commented; u will get output like "Count = 111111111". Expected.

So... something wrong with my usage in shared variable...? My question:

  1. Why case 1 had 0 with Count?
  2. If case 1 is expected, why case 2 happened?

Env: 1. go version go1.8 linux/amd64 2. 3.10.0-123.el7.x86_64 3. CentOS Linux release 7.0.1406 (Core)

Upvotes: 1

Views: 1493

Answers (2)

typetetris
typetetris

Reputation: 4867

Without any synchronisation you have no guarantees at all.

There could be multiple reasons, why you see 'Count = 0' in your first case:

  1. You have a multi processor or multi core system and one unit (cpu or core) is happily churning away at the for loop, while the other sleeps for one second and prints the line you are seeing afterwards. It would be completely legal for the compiler to generate machine code, which loads the value into some register and only ever increase that register in the for loop. The memory location can be updated, when the function is done with the variable. In case of an infinite for loop, that ist never. As you, the programmer told the compiler, that there is no contention about that variable, by omitting any synchronisation.

    In your mutex version the synchronisation primitives tell the compiler, that there might be some other thread taking the mutex, so it needs to write back the value from the register to the memory location before unlocking the mutex. At least one can think about it like that. What really happens that the unlock and a later lock operation introduce a happens before relation between the two go routines and this gives the guarantee, that we will see all writes to variables from before the unlock in one thread after the lock operation in the other thread, as described in go memory model locks howsoever this is implemented.

  2. The Go runtime scheduler doesn't run the for loop at all, until the sleep in the main go routine is done. (Isn't likely, but, if I recall correctly, there is not guarantee that this isn't happening.) Sadly there is not much official documentation available about how the scheduler works in go, but it can only schedule a goroutine at certain points, it is not really preemptive. The consequences of this are severe. For example you could make your program run forever in some versions of go, by firing up as many go routines, as you had cores, doing endless for loops only incrementing a variable. There was no core left for the main go routine (which could end the program) and the scheduler can't preempt a go routine in an endless for loop doing only simple stuff, like incrementing a variable. I don't know, if that is changed now.

  3. as others pointed out, that is a data race, google it and read up about it.

The difference between your versions there only line 16 is commented/uncommented is likely only because of run time, as printing to a terminal can be pretty slow.

For a correct program, you need to additionally lock the mutex after your sleep in you main program and before the fmt.Println and unlock it afterwards. But there can't be a deterministic expectation about the output, as the result will vary with machine/os/...

Upvotes: 1

peterSO
peterSO

Reputation: 166549

You have a data race on count. The results are undefined.

package main

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

var count = uint64(0)

//var l sync.Mutex

func add() {
    for {
        //              l.Lock()
        //              fmt.Println("Start ++")
        count++
        //              l.Unlock()
    }
}

func main() {
    go add()
    time.Sleep(1 * time.Second)
    fmt.Println("Count =", count)
}

Output:

$ go run -race racer.go
==================
WARNING: DATA RACE
Read at 0x0000005995b8 by main goroutine:
  runtime.convT2E64()
      /home/peter/go/src/runtime/iface.go:255 +0x0
  main.main()
      /home/peter/gopath/src/so/racer.go:25 +0xb9

Previous write at 0x0000005995b8 by goroutine 6:
  main.add()
      /home/peter/gopath/src/so/racer.go:17 +0x5c

Goroutine 6 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:23 +0x46
==================
Count = 42104672
Found 1 data race(s)
$ 

References:

Benign data races: what could possibly go wrong?

Upvotes: 6

Related Questions