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

我的任务是在 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


也许你能帮我解释一下这种行为?为什么总迭代计数器和值本身是不确定的?


慕村9548890
浏览 109回答 2
2回答

慕标琳琳

这是竞争条件的经典例子。value++不是原子操作,因此无法保证在没有同步的情况下从多个线程调用时它会正确或确定地工作。给出一些直觉,value++或多或少等同于value = value + 1.&nbsp;您可以将其视为三个操作,而不是一个:value从内存加载到 CPU 寄存器,增加寄存器中的值(您不能直接修改内存),将值存储回内存。两个线程可能同时加载相同的值,增加它,得到相同的结果,然后将它写回,所以它实际上增加了value1,而不是两个。由于线程之间的操作顺序是不确定的,因此结果也是不确定的。同样的效果发生在totalOps.&nbsp;但是,totalIncOps和totalDecOps只能由单个线程修改/读取,因此这里没有竞争,它们的最终值是确定性的。

弑天下

因为对变量 value、totalOps、totalIncOps 和 totalDecOps 的操作没有被锁定添加互斥锁应该有所帮助。Go race detector 功能会发现这个错误var m sync.Mutexfunc increaseByOne(N int, waitGroup *sync.WaitGroup) {&nbsp; &nbsp; for i := 0; i < N; i++ {&nbsp; &nbsp; &nbsp; &nbsp; m.Lock()value++&nbsp; &nbsp; &nbsp; &nbsp; // Collecting stats&nbsp; &nbsp; &nbsp; &nbsp; totalOps++&nbsp; &nbsp; &nbsp; &nbsp; totalIncOps++&nbsp; &nbsp; &nbsp; &nbsp; m.Unlock()&nbsp; &nbsp; }&nbsp; &nbsp; waitGroup.Done()}// Same with decreasefunc decreaseByOne(N int, waitGroup *sync.WaitGroup) {&nbsp; &nbsp; for i := 0; i < N; i++ {&nbsp; &nbsp; &nbsp; &nbsp; m.Lock()&nbsp; &nbsp; &nbsp; &nbsp; value--&nbsp; &nbsp; &nbsp; &nbsp; // Collecting stats&nbsp; &nbsp; &nbsp; &nbsp; totalOps++&nbsp; &nbsp; &nbsp; &nbsp; totalDecOps++&nbsp; &nbsp; &nbsp; &nbsp; m.Unlock()&nbsp; &nbsp; }&nbsp; &nbsp; waitGroup.Done()}上述方法的替代方法是对计数器使用 Sync.Atomic
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go