【问题标题】:Unexpected behavior in code using sync/atomic package for synchronization使用 sync/atomic 包进行同步的代码中的意外行为
【发布时间】:2020-11-06 09:51:11
【问题描述】:

下面是我在学习 Golang 中的 goroutine 时正在研究的一个示例。在下面的代码中,我们生成了 30 个 goroutine,每个 goroutine 都访问一个名为 ordersProcessed 的共享变量。该示例代表一个收银员处理订单。一旦 ordersProcessed 超过 10,我们就会打印出收银员无法再接单。

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

func main() {
    var (
      wg sync.WaitGroup
      ordersProcessed int64
    )
    // This does not work as expected
    cashier := func(orderNum int) {
        value := atomic.LoadInt64(&ordersProcessed)
        fmt.Println("Value is ", value)
        if value < 10 {
            // Cashier is ready to serve!
            fmt.Println("Proessing order", orderNum)
            atomic.AddInt64(&ordersProcessed, 1)
        } else {
            // Cashier has reached the max capacity of processing orders.
            fmt.Println("I am tired! I want to take rest!", orderNum)
        }
        wg.Done()
    }

    for i := 0; i < 30; i++ {
        wg.Add(1)
        go func(orderNum int) {
            // Making an order
            cashier(orderNum)
        }(i)

    }
    wg.Wait()
}

我希望看到 10 个订单的已处理消息,并且以后无法处理。但是,所有 30 个订单都会得到处理。我使用了 sync/atomic 包来同步对 ordersProcessed 变量的访问,但是它的值总是被每个 goroutine 读取为 0。但是,如果我将上面的代码更改为使用下面的互斥锁,它会按预期工作:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var (
       wg sync.WaitGroup
       ordersProcessed int64
       mutex sync.Mutex
    )
    // This works as expected
    cashier := func(orderNum int) {
        mutex.Lock()
        if ordersProcessed < 10 {
            // Cashier is ready to serve!
            fmt.Println("Processing order", orderNum)
            ordersProcessed++
        } else {
            // Cashier has reached the max capacity of processing orders.
            fmt.Println("I am tired! I want to take rest!", orderNum)
        }
        mutex.Unlock()
        wg.Done()
    }

    for i := 0; i < 30; i++ {
        wg.Add(1)
        go func(orderNum int) {
            // Making an order
            cashier(orderNum)
        }(i)

    }
    wg.Wait()
}

谁能告诉我我使用 sync/atomic 包同步对 ordersProcessed 变量的访问的方式有什么问题?

【问题讨论】:

    标签: go synchronization goroutine


    【解决方案1】:

    你使用了 sync/atomic 包,但是你没有同步 goroutines。

    当你启动 30 个 goroutine 时,每个 goroutine 从读取共享变量开始,并递增它。如果所有 goroutine 都读取该变量,它们都将读取 0。这里的问题是,当一个 goroutine 正在处理该变量时,您没有阻止其他 goroutine 修改该变量。程序运行后,共享变量可以是 10 到 30 之间的任何值,具体取决于 goroutine 的交错方式。

    您的第二个实现是正确的,它可以防止其他 goroutine 在其中一个正在处理共享变量时读取和修改共享变量。

    【讨论】:

    • 感谢您的回复。但是来自 sync/atomic 包的 AddInt64 和 LoadInt64 方法是否应该防止竞争条件并只允许同步访问共享数据,在我的情况下是 ordersProcessed 变量?正如您在我的代码中看到的那样,我使用 atomic.LoadInt64 读取变量的值并使用 atomic.AddInt64 对其进行修改,而不是直接执行这些操作。
    • 对共享变量的访问是原子的。但是您是先比较,然后根据条件添加。那不是原子的。没有什么能阻止所有的 goroutine 读取,得到一个零,然后进入第一个 if 块。
    • 好的,我现在明白了。这是因为我先阅读,所以每个 goroutine 都读取 0 的值。现在很有意义,非常感谢 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-03
    • 2017-08-16
    • 2019-11-28
    相关资源
    最近更新 更多