3
votes

I have a question about concurrency in GoLang. Here is a sample code in GoLang

package main

import(
    "fmt"
    "time"
)

var m int
func add(i int){
    m++
}

func main() {
    m = 0
    for i:=0;i<100;i++{
        go add(i)
    }
    time.Sleep(time.Millisecond * 1000)
    fmt.Println(m)
}

When I execute it I always have the same result 100, even if I execute it several times.

If I do the same code in C (without mutex), sometimes I have different results.

And my question, I would like to know if GoLang implicitly manages access to a shared variable using an internal mechanism ?

Thank you.

6

6 Answers

5
votes

No. For example, using your program,

$ go run -race dz00dz.go
==================
WARNING: DATA RACE
Read at 0x000000595200 by goroutine 7:
  main.add()
     /home/peter/gopath/src/dz00dz.go:11 +0x3d

Previous write at 0x000000595200 by goroutine 6:
  main.add()
      /home/peter/gopath/src/dz00dz.go:11 +0x59

Goroutine 7 (running) created at:
  main.main()
      /home/peter/gopath/src/dz00dz.go:17 +0x76

Goroutine 6 (finished) created at:
  main.main()
      /home/peter/gopath/src/dz00dz.go:17 +0x76
==================
100
Found 1 data race(s)
exit status 66

References:

Introducing the Go Race Detector

3
votes

I am getting the same results here, but not if I replace

m++

with

func add(i int) {
    for j := 0; j < 100000; j++ {
        m++
    }
}

In the latter case, I confirmed using scheduler tracing that the Go runtime distributed the work on several processor cores, which explains the discrepancy because, indeed, m is not protected against race conditions. So why does it not happen in the former case ? Probably because a simple integer increment is too short for the Go scheduler to assign the goroutines to several threads, so they execute in sequence, in the same thread. And why does it happen in C : because you manually distribute the calculations on several threads, so the operating system scheduler may decide to execute them on several processor cores.

1
votes

You have a race condition here, try to test your code with:

go test -race

It means that m++ is not thread-safe, try something like this:

var (
    m int
    mu *sync.RWMutex
)

func add(i int){
    mu.Lock()
    m++
    mu.Unlock()
}

Also I see this code is a bit dirty: 1. Remove m = 0 2. Replace Sleep with WaitGroup 3. Why do you pass i but do not use it?

0
votes

No, you cant get the same result.

package main

import (
    "fmt"
    "time"
)

var m int

func add(i int) {
    m++
}

func main() {
    m = 0
    for i := 0; i < 10000; i++ {
        go add(i)
    }
    time.Sleep(time.Millisecond * 1000)
    fmt.Println(m)
}
0
votes
// This sample program demonstrates how to create goroutines and
// how the goroutine scheduler behaves with three logical processors.

package main

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

func main() {
    // Allocate three logical processors for the scheduler to use.
    runtime.GOMAXPROCS(3)

    // processTest is used to wait for the program to finish.
    var processTest sync.WaitGroup
    // Add a count of three, one for each goroutine.
    processTest.Add(3)

    // Declaration of three anonymous function and create a goroutine.
    go func() {
        defer processTest.Done()
        for i := 0; i < 30; i++ {
            for j := 51; j <= 100; j++ {
                fmt.Printf(" %d", j)
                if j == 100{
                    fmt.Println()
                }
            }
        }
    }()
    go func() {
        defer processTest.Done()
        for j := 0; j < 10; j++ {
            for char := 'A'; char < 'A'+26; char++ {
                fmt.Printf("%c ", char)
                if char == 'Z' {
                    fmt.Println()
                }

            }
        }
    }()
    go func() {
        defer processTest.Done()
        for i := 0; i < 30; i++ {
            for j := 0; j <= 50; j++ {
                fmt.Printf(" %d", j)
                if j == 50 {
                    fmt.Println()
                }
            }
        }
    }()

    // Wait for the goroutines to finish.
    processTest.Wait()  
}

If we give the scheduler more than one logical processor to use, we’ll see different behavior in the output of our example programs. If you run the program, you’ll see that the goroutines are running in parallel. Multiple goroutines start running, and the letters and numbers in the display are mixed. The output is based on running the program on an eight-core machine, so each goroutine is running on its own core. http://www.golangprograms.com/this-sample-program-demonstrates-how-to-create-multiple-goroutines-and-how-the-goroutine-scheduler-behaves-with-three-logical-processors.html

0
votes

Goroutines do not run in parallel. Go work scheduler manages all the goroutines such that all the goroutines are run effectively. Since, goroutines are not running in parallel, the answer would always be 100 for the above program. But, there is one setting GOMAXPROCS which is by default 1, but if you set it any number N > 1, the answer would be different everytime since N goroutines are running in parallel.