为了账号安全,请及时绑定邮箱和手机立即绑定

使用 WaitGroup 测试竞争条件时的意外行为

使用 WaitGroup 测试竞争条件时的意外行为

Go
慕村9548890 2023-05-15 15:37:22
我的任务是在 Go 中模拟竞争条件。但是,我遇到了一个我无法解释的案例。下面的代码片段package mainimport (    "fmt"    "sync")var value, totalOps, totalIncOps, totalDecOps intfunc 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 conditionfunc 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 1func increaseByOne(N int, waitGroup *sync.WaitGroup) {    for i := 0; i < N; i++ {        value++        // Collecting stats        totalOps++        totalIncOps++    }    waitGroup.Done()}// Same with decreasefunc 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也许你能帮我解释一下这种行为?为什么总迭代计数器和值本身是不确定的?
查看完整描述

2 回答

?
慕标琳琳

TA贡献1830条经验 获得超9个赞

这是竞争条件的经典例子。value++不是原子操作,因此无法保证在没有同步的情况下从多个线程调用时它会正确或确定地工作。

给出一些直觉,value++或多或少等同于value = value + 1. 您可以将其视为三个操作,而不是一个:value从内存加载到 CPU 寄存器,增加寄存器中的值(您不能直接修改内存),将值存储回内存。两个线程可能同时加载相同的值,增加它,得到相同的结果,然后将它写回,所以它实际上增加了value1,而不是两个。

由于线程之间的操作顺序是不确定的,因此结果也是不确定的。

同样的效果发生在totalOps. 但是,totalIncOpstotalDecOps只能由单个线程修改/读取,因此这里没有竞争,它们的最终值是确定性的。


查看完整回答
反对 回复 2023-05-15
?
弑天下

TA贡献1818条经验 获得超8个赞

因为对变量 value、totalOps、totalIncOps 和 totalDecOps 的操作没有被锁定


添加互斥锁应该有所帮助。Go race detector 功能会发现这个错误


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


查看完整回答
反对 回复 2023-05-15
  • 2 回答
  • 0 关注
  • 107 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信