Go语言中的数据竞争:为什么会在10-11毫秒以下发生?

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

Data race in Go: Why does it happen below 10-11ms?

问题

这是我运行的代码:

package main

import (
	"fmt"
	"time"
)

const delay = 9 * time.Millisecond

func main() {
	n := 0
	go func() {
		time.Sleep(delay)
		n++
	}()
	fmt.Println(n)
}

这是我使用的命令:

go run -race data_race_demo.go

我注意到的行为是:

  • delay设置为9ms或更低时,总是检测到数据竞争(程序抛出Found 1 data race(s)
  • delay设置为12ms或更高时,从不检测到数据竞争(程序只是打印0
  • delay设置为10到11ms时,数据竞争会间歇性地发生(即有时打印0,有时抛出Found 1 data race(s)

为什么会在大约10-11ms时发生这种情况?

我在darwin/amd64上使用的是Go 1.16.3,如果有关系的话。

英文:

Here is the code I ran:

package main

import (
	"fmt"
	"time"
)

const delay = 9 * time.Millisecond

func main() {
	n := 0
	go func() {
		time.Sleep(delay)
		n++
	}()
	fmt.Println(n)
}

Here is the command I used:

go run -race data_race_demo.go

Here is the behavior I noticed:

  • With delay set to 9ms or lower, data race is always detected (program throws Found 1 data race(s))
  • With delay set to 12ms or higher, data race is never detected (program simply prints 0)
  • With delay set to 10 to 11ms, data race occurs intermittently (that is, sometimes prints 0 and sometimes throws Found 1 data race(s))

Why does this happen at around 10-11ms?

I'm using Go 1.16.3 on darwin/amd64, if that matters.

答案1

得分: 7

你有两个goroutine:main和你启动的另一个。它们在没有同步的情况下访问n变量(其中一个是写入操作),这就是数据竞争。

是否检测到此竞争取决于是否发生了这种有竞争的访问。当main()函数结束时,你的应用程序也会结束,它不会等待其他非main的goroutine完成。

如果你增加睡眠延迟,main()函数会在睡眠结束之前结束,并且不会等待n++的竞争写入发生,因此不会检测到任何问题。如果睡眠时间很短,比fmt.Println()的执行时间还要短,那么竞争写入就会发生并被检测到。

10毫秒没有什么特殊之处,那只是大致执行fmt.Println()并在你的环境中终止应用程序所需的时间。如果在Println()语句之前执行其他"耗时"的任务,例如:

for i := 0; i < 5_000_000_000; i++ {
}
fmt.Println(n)

即使睡眠时间为50毫秒,竞争仍然会被检测到(因为该循环需要一些时间来执行,允许在读取n进行fmt.Println()调用和应用程序终止之前发生竞争写入)。(简单的time.Sleep()也可以实现,我只是不希望有人错误地得出它们以某种方式"相互作用"的错误结论。)

英文:

You have 2 goroutines: the main and the one you launch. They access the n variable (and one is a write) without synchronization: that's a data race.

Whether this race is detected depends on whether this racy access occurs. When the main() function ends, your app ends as well, it does not wait for other non-main goroutines to finish.

If you increase the sleep delay, main() will end sooner than the end of sleep and will not wait for the n++ racy write to occur, so nothing is detected. If sleep is short, shorter than the fmt.Prinln() execution time, the racy write occurs and is detected.

There's nothing special about the 10ms. That's just the approximated time it takes to execute fmt.Println() and terminate your app in your environment. If you do other "lengthy" task before the Println() statement, such as this:

for i := 0; i &lt; 5_000_000_000; i++ {
}
fmt.Println(n)

The race will be detected even with 50ms sleep too (because that loop will take some time to execute, allowing the racy write to occur before n is read for the fmt.Println() call and the app terminated). (A simple time.Sleep() would also do, I just didn't want anyone to draw the wrong conclusion that they "interact" with each other somehow.)

huangapple
  • 本文由 发表于 2021年5月21日 20:14:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/67636634.html
匿名

发表评论

匿名网友

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

确定