数据竞争,两个 goroutine 同时访问相同的值。

huangapple go评论81阅读模式
英文:

data-race, two goroutines plus same val

问题

考虑以下代码,在我看来,val的值应该在100和200之间,但最终它总是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("执行进程[%s],val的值为%d\n", proc, val)
        time.Sleep(5 * time.Millisecond)
    }
}

为什么val最终总是200呢?

英文:

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(&quot;使用cpu数量&quot;, num)
	go add(&quot;A&quot;)
	go add(&quot;B&quot;)
	time.Sleep(1 * time.Second)
	fmt.Println(&quot;val的最终结果&quot;, val)
}

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

why val always is 200 at last?

答案1

得分: 1

你的代码有两个问题:

  1. 你存在数据竞争 - 在没有同步的情况下并发地写入和读取 val。这种情况的存在使得对程序结果的推理毫无意义。
  2. main() 函数中的 1 秒睡眠时间太短了 - 在 1 秒之后,goroutine 可能还没有完成。你期望 fmt.Printf 几乎不需要时间,但是控制台输出确实需要相当长的时间(在某些操作系统上比其他操作系统更长)。因此,循环不会花费 100 * 5 = 500 毫秒,而是更长的时间。

下面是修复后的版本,它使用原子操作增加 val,并正确等待两个 goroutine 完成,而不是假设它们会在 1 秒内完成。

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("执行进程[%s],val为%d\n", proc, tmp)
        time.Sleep(5 * time.Millisecond)
    }
    wg.Done()
}
英文:

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(&quot;使用cpu数量&quot;, num)
	var wg sync.WaitGroup
	wg.Add(2)
	go add(&quot;A&quot;, &amp;wg)
	go add(&quot;B&quot;, &amp;wg)
	wg.Wait()
	fmt.Println(&quot;val的最终结果&quot;, atomic.LoadInt32(&amp;val))
}

func add(proc string, wg *sync.WaitGroup) {
	for i := 0; i &lt; 100; i++ {
		tmp := atomic.AddInt32(&amp;val, 1)
		fmt.Printf(&quot;execute process[%s] and val is %d\n&quot;, proc, tmp)
		time.Sleep(5 * time.Millisecond)
	}
	wg.Done()
}

答案2

得分: 0

递增整数只需要几纳秒的时间,而每个goroutine在每次递增之间等待5毫秒。

也就是说,每个goroutine只花费大约千万分之一的时间来执行操作,其余时间是休眠。因此,干扰发生的可能性非常低(因为两个goroutine需要同时执行操作)。

即使两个goroutine都在相等的定时器上,time库的实际精度也远远不及产生一致的纳秒级碰撞所需的精度。此外,goroutine还执行了一些打印操作,这将进一步使时间差异。


正如评论中指出的,你的代码仍然存在数据竞争(似乎这是你的意图),这意味着我们无法确定程序的输出,尽管有任何观察结果。它可能输出100到200之间的任何数字,或者完全不同的数字。

英文:

Incrementing the integer will take only on the order of a few nanoseconds, while each goroutine is waiting 5 milliseconds between each increment.

Meaning, each goroutine is only spending about a 1,000,000th of the time actually doing the operation, the rest of the time is sleeping. Therefore, the likelihood of an interference happening is quite low (since both goroutines would need to be doing the operation simultaneously).

Even if both goroutines are on equal timers, the practical precision of the time library is nowhere near the nanosecond scale required to produce collisions consistently. Plus, the goroutines are doing some printing, which will further diverge the timings.


As pointed out in the comments, your code does still have a data race (as is your intention, seemingly), meaning that we cannot say anything for certain about the output of your program, despite any observations. It's possible that it would output any number from 100-200, or different numbers entirely.

huangapple
  • 本文由 发表于 2021年9月15日 01:21:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/69182093.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定