英文:
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("使用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?
答案1
得分: 1
你的代码有两个问题:
- 你存在数据竞争 - 在没有同步的情况下并发地写入和读取
val
。这种情况的存在使得对程序结果的推理毫无意义。 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:
- You have a data race - concurrently writing and reading
val
without synchronization. The presence of it makes reasoning about program outcome meaningless. - The sleep in
main()
of 1 second is too short - the goroutines may not be done yet after 1 second. You expectfmt.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()
}
答案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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论