2

I'm facing a problem in golang

var a = 0
func main() {
        go func() {
                for {
                        a = a + 1
                }
        }()
        time.Sleep(time.Second)
        fmt.Printf("result=%d\n", a)
}
  • expected: result=(a big int number)
  • result: result=0
Ali Zohrevand
  • 393
  • 4
  • 20
  • 9
    You have a data race so any result is undefined. The busy loop doesn’t help either, since it’s always going to interfere with execution. – JimB Jun 20 '19 at 03:26

3 Answers3

3

You have a race condition, run your program with -race flag

go run -race main.go
==================
WARNING: DATA RACE
Read at 0x0000005e9600 by main goroutine:
  main.main()
      /home/jack/Project/GoProject/src/gitlab.com/hooshyar/GoNetworkLab/StackOVerflow/race/main.go:17 +0x6c

Previous write at 0x0000005e9600 by goroutine 6:
  main.main.func1()
      /home/jack/Project/GoProject/src/gitlab.com/hooshyar/GoNetworkLab/StackOVerflow/race/main.go:13 +0x56

Goroutine 6 (running) created at:
  main.main()
      /home/jack/Project/GoProject/src/gitlab.com/hooshyar/GoNetworkLab/StackOVerflow/race/main.go:11 +0x46
==================
result=119657339
Found 1 data race(s)
exit status 66

what is solution?
There is some solution, A solution is using a mutex:

var a = 0
func main() {
    var mu sync.Mutex

    go func() {
        for {
            mu.Lock()
            a = a + 1
            mu.Unlock()
        }
    }()
    time.Sleep(3*time.Second)
    mu.Lock()
    fmt.Printf("result=%d\n", a)
    mu.Unlock()
}

before any read and write lock the mutex and then unlock it, now you don not have any race and resault will bi big int at the end.
For more information read this topic.
Data races in Go(Golang) and how to fix them and this

Golang concurrency - data races

Ali Zohrevand
  • 393
  • 4
  • 20
  • Thanks for explanation. However when run the totally same code in c, I got different result(as the solution with mutex). So this is a feature or bug of go-routine? One second is a long period for processor, but the result is still 0. Is that mean during this period, the second routine has never pass the value of variable to main routine? – justbeatdruid Jun 20 '19 at 09:51
  • the results will be different every time to run the code even in code written in go, it is not very unusual to have another result in c program. I got result=53413251 it was not zero. – Ali Zohrevand Jun 20 '19 at 09:55
  • bizzare.. I got always 0 even sleep for 10 seconds, with go version go1.12.5 darwin/amd64.. – justbeatdruid Jun 20 '19 at 11:35
  • 1
    @justbeatdruid: it's not bizarre, you have a data race, so the output could literally be anything: ([Benign Data Races: What Could Possibly Go Wrong](https://software.intel.com/en-us/blogs/2013/01/06/benign-data-races-what-could-possibly-go-wrong)). Also, since there's no synchronization between reading and writing of `a`, the compiler and cpu can freely reorder those as they see fit. – JimB Jun 20 '19 at 14:54
  • FWIW @justbeatdruid, if you actually wanted to run this exact code, and you cared about performance, you should synchronize with atomics (see https://golang.org/pkg/sync/atomic/) rather than locking/unlocking a mutex on every loop iteration. But Ali is absolutely correct - his code works, and fixes the data race, and performance is not really the point here. You need to understand the rules for when changes in one goroutine are guaranteed by the language to be visible in another (see my answer for a slightly longer explanation) – JVMATL Jun 21 '19 at 04:52
1

As other writers have mentioned, you have a data race, but if you are comparing this behavior to, say, a program written in C using pthreads, you are missing some important data. Your problem is not just about timing, it's about the very language definition. Because concurrency primitives are baked into the language itself, the Go language memory model (https://golang.org/ref/mem) describes exactly when and how changes in one goroutine -- think of goroutines as "super-lightweight user-space threads" and you won't be too far off -- are guaranteed to be visible to code running in another goroutine.

Without any synchronizing actions, like channel sends/receives or sync.Mutex locks/unlocks, the Go memory model says that any changes you make to 'a' inside that goroutine don't ever have to be visible to the main goroutine. And, since the compiler knows that, it is free to optimize away pretty much everything in your for loop. Or not.

It's a similar situation to when you have, say, a local int variable in C set to 1, and maybe you have a while loop reading that variable in a loop waiting for it to be set to 0 by an ISR, but then your compiler gets too clever and decides to optimize away the test for zero because it thinks your variable can't ever change within the loop and you really just wanted an infinite loop, and so you have to declare the variable as volatile to fix the 'bug'.

If you are going to be working in Go, (my current favorite language, FWIW,) take time to read and thoroughly grok the Go memory model linked above, and it will really pay off in the future.

JVMATL
  • 2,064
  • 15
  • 25
0

Your program is running into race condition. go can detect such scenarios.

Try running your program using go run -race main.go assuming your file name is main.go. It will show how race occured , attempted write inside the goroutine , simultaneous read by the main goroutine. It will also print a random int number as you expected.

Abhishek Jha
  • 935
  • 2
  • 10
  • 22