Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Race condition even when using sync.Mutex in golang

Complete code is here: https://play.golang.org/p/ggUoxtcv5m go run -race main.go says there is a race condition there which I fail to explain. The program outputs correct final result, though.

The essence:

type SafeCounter struct {
    c int
    sync.Mutex
}

func (c *SafeCounter) Add() {
    c.Lock()
    c.c++
    c.Unlock()
}

var counter *SafeCounter = &SafeCounter{} // global

use *SafeCounter in incrementor:

func incrementor(s string) {
    for i := 0; i < 20; i++ {
        x := counter
        x.Add()
        counter = x
    }
}

The incrementor method is spawned twice in main:

func main() {
    go incrementor()
    go incrementor()
    // some other non-really-related stuff like
    // using waitGroup is ommited here for problem showcase
}

So, as I said, go run -race main.go will always say there is a race cond found.

Also, the final result is always correct (at least I've run this program for a number of times and it always say final counter is 40, which is correct). BUT, the program prints incorrect values in the beginning so you can get something like:

Incrementor1: 0 Counter: 2
Incrementor2: 0 Counter: 3
Incrementor2: 1 Counter: 4
// ang the rest is ok

so, printing out 1 is missing there.

Can somebody explain why there is a race condition there is my code?

like image 902
Nemoden Avatar asked May 03 '17 23:05

Nemoden


People also ask

How does Mutex prevent race condition?

Mutexes and race conditionsMutual exclusion locks (mutexes) can prevent data inconsistencies due to race conditions. A race condition often occurs when two or more threads must perform operations on the same memory area, but the results of computations depends on the order in which these operations are performed.

How do you avoid race conditions in Golang?

Memory access synchronization primitives in Golang allow your program to lock and unlock operations so that they can run in a concurrency-safe manner. This ensures that a program that shares data among different goroutines runs correctly and avoids data race conditions.

What is sync Mutex Golang?

A Mutex is used to provide a locking mechanism to ensure that only one Goroutine is running the critical section of code at any point in time to prevent race conditions from happening. Mutex is available in the sync package. There are two methods defined on Mutex namely Lock and Unlock.

What does sync Mutex lock?

A Mutex guarantees only that if something has locked it, it cannot be locked again by something else until the lock is first released. It is up to you to use it correctly, by ensuring that you obtain a lock before you try to access whatever you want protected by the lock, as you've done in your example main .


2 Answers

You have a number of race conditions, all pointed out specifically by the race detector:

    x := counter      // this reads the counter value without a lock
    fmt.Println(&x.c)
    x.Add()
    counter = x       // this writes the counter value without a lock
    time.Sleep(time.Duration(rand.Intn(3)) * time.Millisecond)
    fmt.Println(s, i, "Counter:", x.c) // this reads the c field without a lock
  • race #1 is between the read and the write of the counter value in incrementor

  • race #2 is between the concurrent writes to the counter value in incrementor

  • race #3 is between the read of the x.c field in fmt.Println, and the increment to x.c in the Add method.

like image 167
JimB Avatar answered Nov 03 '22 06:11

JimB


The two lines that read and write the counter pointer are not protected by the mutex and are done concurrently from multiple goroutines.

func incrementor(s string) {
    for i := 0; i < 20; i++ {
        x := counter  // <-- this pointer read
        x.Add()
        counter = x   // <-- races with this pointer write
    }
}
like image 34
ChrisH Avatar answered Nov 03 '22 06:11

ChrisH