简体   繁体   中英

Why is there a race condition detected on Printf in this code by Go

I wrote some simple Go code to understand race conditions as shown:

package main

import (
    "fmt"
    "sync"
)

type outer struct {
    sync.Mutex
    num int
    foo string
}

func (outer *outer) modify(wg *sync.WaitGroup) {
    outer.Lock()
    defer outer.Unlock()
    outer.num = outer.num + 1
    wg.Done()
}

func main() {
    outer := outer{
        num: 2,
        foo: "hi",
    }
    var w sync.WaitGroup
    for j := 0; j < 5000; j++ {
        w.Add(1)
        go outer.modify(&w)
    }
    w.Wait()
    fmt.Printf("Final is %+v", outer)

}

When I run above, the answer printed is always correct ie num is always 5002. Without the lock, the answer is unpredictable as expected because of race between the goroutines created in the forloop.

However, when I run this with -race, the following race condition is detected:


go run -race random.go
==================
WARNING: DATA RACE
Read at 0x00c00000c060 by main goroutine:
  main.main()
      random.go:32 +0x15d

Previous write at 0x00c00000c060 by goroutine 22:
  sync/atomic.AddInt32()
      /usr/local/go/src/runtime/race_amd64.s:269 +0xb
  sync.(*Mutex).Unlock()
      /usr/local/go/src/sync/mutex.go:182 +0x54
  main.(*outer).modify()
      random.go:19 +0xb7

Goroutine 22 (finished) created at:
  main.main()
      random.go:29 +0x126
==================
Final is {Mutex:{state:0 sema:0} num:5002 foo:hi}Found 1 data race(s)
exit status 66

Ie. it is detecting a race between the final Printf and one random go routine created before it. Since I am using a wait to synchronize, all go routines are completed by the time we get to Printf.

What is the reason for the race being reported?

Do I need locking on printing the structure as well?

Improper use of sync.WaitGroup is what's causing your race condition. Either of these should work properly:

func (outer *outer) modify(wg *sync.WaitGroup) {
    outer.Lock()
    outer.num = outer.num + 1
    outer.Unlock()
    wg.Done()
}
func (outer *outer) modify(wg *sync.WaitGroup) {
    outer.Lock()
    defer wg.Done()
    defer outer.Unlock()
    outer.num = outer.num + 1
}

wg.Done() should be called AFTER unlocking the mutex (deferred calls are made in LIFO fashion), since calling it before will cause the Printf() call to race the last outer.Unlock() call for access to outer .

package main

import (
    "fmt"
    "sync"
)

type outer struct {
    *sync.Mutex
    num int
    foo string
}

func (outer *outer) modify(wg *sync.WaitGroup) {
    outer.Lock()
    defer outer.Unlock()
    outer.num++
    wg.Done()
}

func main() {
    outer := outer{
        Mutex: &sync.Mutex{},
        num:   2,
        foo:   "hi",
    }
    w := &sync.WaitGroup{}
    for j := 0; j < 5000; j++ {
        w.Add(1)
        go outer.modify(w)
    }
    w.Wait()
    fmt.Printf("Final is %+v", outer)
}

change sync.Mutex to pointer.

I thinks it's due to sync.Mutex is value in your version

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM