我有一个任务可以模拟Go中的比赛条件。但是,我遇到了一个无法解释的案例。下面的代码段
package main
import (
"fmt"
"sync"
)
var value, totalOps, totalIncOps, totalDecOps int
func main() {
fmt.Println("Total value: ", simulateRacing(10000))
fmt.Print("Total iterations: ", totalOps)
fmt.Print(" of it, increments: ", totalIncOps)
fmt.Print(", decrements: ", totalDecOps)
}
// Function to simulate racing condition
func simulateRacing(iterationsNumber int) int {
value = 0
// Define WaitGroup
var waitGroup sync.WaitGroup
waitGroup.Add(2)
go increaseByOne(iterationsNumber, &waitGroup)
go decreaseByOne(iterationsNumber, &waitGroup)
waitGroup.Wait()
return value
}
// Function to do N iterations, each time increasing value by 1
func increaseByOne(N int, waitGroup *sync.WaitGroup) {
for i := 0; i < N; i++ {
value++
// Collecting stats
totalOps++
totalIncOps++
}
waitGroup.Done()
}
// Same with decrease
func decreaseByOne(N int, waitGroup *sync.WaitGroup) {
for i := 0; i < N; i++ {
value--
// Collecting stats
totalOps++
totalDecOps++
}
waitGroup.Done()
}
据我了解,由于我们执行相同数量的递增和递减操作,因此每次都应产生一致的(确定性)结果,并且WaitGroup确保两个函数都将执行。
但是,每次输出都是不同的,只有增量和减量计数器保持不变。 总价值:2113 总迭代次数:17738次,增量:10000次,减量:10000次 和 总价值:35 总迭代次数:10741次,增量:10000次,减量:10000次
也许您可以帮助我解释这种行为?为什么总迭代计数和值本身是不确定的?
答案 0 :(得分:2)
这是种族状况的经典示例。 value++
不是原子操作,因此不能保证当从多个线程调用而没有同步时,它将正确或确定地工作。
为了提供一些直觉,value++
或多或少与value = value + 1
等效。您可以将其视为三个操作,而不是一个操作:将value
从内存加载到CPU寄存器,增加寄存器中的值(您不能直接修改内存),然后将值存储回记忆。两个线程可以同时加载相同的值,将其增加,获得相同的结果,然后再写回,因此它实际上将value
加1,而不是两个。
由于线程之间的操作顺序是不确定的,因此结果也是不确定的。
totalOps
也会产生相同的效果。但是,totalIncOps
和totalDecOps
只能由单个线程修改/读取,因此这里没有任何竞争,它们的最终值是确定的。
答案 1 :(得分:1)
因为对变量值的操作未锁定totalOps,totalIncOps和totalDecOps
添加互斥锁会有所帮助。 Go竞赛检测器功能会发现此故障
var m sync.Mutex
func increaseByOne(N int, waitGroup *sync.WaitGroup) {
for i := 0; i < N; i++ {
m.Lock()
value++
// Collecting stats
totalOps++
totalIncOps++
m.Unlock()
}
waitGroup.Done()
}
// Same with decrease
func decreaseByOne(N int, waitGroup *sync.WaitGroup) {
for i := 0; i < N; i++ {
m.Lock()
value--
// Collecting stats
totalOps++
totalDecOps++
m.Unlock()
}
waitGroup.Done()
}
上述方法的替代方法是对计数器使用Sync.Atomic