godata-race

data-race, two goroutines plus same val


consider below code, in my opinon, val will between 100 and 200, but it's always 200

var val = 0

func main() {
    num := runtime.NumCPU()
    fmt.Println("使用cpu数量", num)
    go add("A")
    go add("B")
    time.Sleep(1 * time.Second)
    fmt.Println("val的最终结果", val)
}

func add(proc string) {
    for i := 0; i < 100; i++ {
        val++
        fmt.Printf("execute process[%s] and val is %d\n", proc, val)
        time.Sleep(5 * time.Millisecond)
    }
}

why val always is 200 at last?


Solution

  • There are 2 problems with your code:

    1. You have a data race - concurrently writing and reading val without synchronization. The presence of it makes reasoning about program outcome meaningless.
    2. The sleep in main() of 1 second is too short - the goroutines may not be done yet after 1 second. You expect fmt.Printf to take no time at all, but console output does take significant amount of time (on some OS longer than others). So the loop won't take 100 * 5 = 500 milliseconds, but much, much longer.

    Here's a fixed version that atomically increments val and properly waits for both goroutines to finish instead of assuming that they will be done within 1 second.

    var val = int32(0)
    
    func main() {
        num := runtime.NumCPU()
        fmt.Println("使用cpu数量", num)
        var wg sync.WaitGroup
        wg.Add(2)
        go add("A", &wg)
        go add("B", &wg)
        wg.Wait()
        fmt.Println("val的最终结果", atomic.LoadInt32(&val))
    }
    
    func add(proc string, wg *sync.WaitGroup) {
        for i := 0; i < 100; i++ {
            tmp := atomic.AddInt32(&val, 1)
            fmt.Printf("execute process[%s] and val is %d\n", proc, tmp)
            time.Sleep(5 * time.Millisecond)
        }
        wg.Done()
    }