在 Go 中使用 sync.Atomic 包防止竞争条件

我在 Go 中实现了以下计数器,我想同时使用它。我正在使用 atomic 包来存储状态,但不确定是否会遇到任何竞争条件。例如,我是否还需要添加一个额外的互斥锁以防止增量低于零,或者原子操作是否提供足够的安全性?谢谢!


type Counter struct {

    counter  uint64

    finished uint32

    sync.Mutex

}


// Inc increments the counter by one

func (c *Counter) Inc() error {

    if c.isFinished() {

        return fmt.Errorf("counter is finished")

    }


    atomic.AddUint64(&c.counter, 1)

    return nil

}


// Dec decrements the counter by one, but prevents the counter from going to zero

func (c *Counter) Dec() {

    // prevent overflow

    if !c.isZero() {

        atomic.AddUint64(&c.counter, ^uint64(0))

    }

}


// Cancel sets the finished flag, and sets counter to zero

func (c *Counter) Cancel() {

    if !c.isFinished() {

        atomic.StoreUint32(&c.finished, 1)

        atomic.StoreUint64(&c.counter, 0)

    }

}


// isFinished returns true if finished

func (c *Counter) isFinished() bool {

    return atomic.LoadUint32(&c.finished) == uint32(1)

}


// isZero returns true if counter is zero

func (c *Counter) isZero() bool {

    return atomic.LoadUint64(&c.counter) == uint64(0)

}

更新:通过使用-race标志运行下面的代码,我能够检测到我确实需要包含互斥锁。


var counter *Counter = &Counter{}


func main() {

    wg := sync.WaitGroup{}


    numberOfLoops := 10

    wg.Add(numberOfLoops)


    for i := 0; i < numberOfLoops; i++ {

        go incrementor(&wg)

    }


    wg.Wait()

    fmt.Println("Final Counter:", counter.counter)

}


func incrementor(wg *sync.WaitGroup) {

    rand.Seed(time.Now().UnixNano())

    for i := 0; i < 20; i++ {

        counter.Inc()

        time.Sleep(time.Duration(rand.Intn(3)) * time.Millisecond)

    }

    wg.Done()

    counter.Cancel()

}


紫衣仙女
浏览 98回答 2
2回答

潇潇雨雨

您不需要额外的互斥锁,您的主要功能是在counter.counter不使用原子负载的情况下读取,而您的incrementor调用wg.Done()before counter.Cancel(),因此您会获得竞争条件。通过在竞争条件解决wg.Done()后移动:counter.Cancel()func main() {&nbsp; &nbsp; wg := sync.WaitGroup{}&nbsp; &nbsp; numberOfLoops := 10&nbsp; &nbsp; wg.Add(numberOfLoops)&nbsp; &nbsp; for i := 0; i < numberOfLoops; i++ {&nbsp; &nbsp; &nbsp; &nbsp; go incrementor(&wg)&nbsp; &nbsp; }&nbsp; &nbsp; wg.Wait()&nbsp; &nbsp; fmt.Println("Final Counter:", counter.counter)}func incrementor(wg *sync.WaitGroup) {&nbsp; &nbsp; rand.Seed(time.Now().UnixNano())&nbsp; &nbsp; for i := 0; i < 20; i++ {&nbsp; &nbsp; &nbsp; &nbsp; counter.Inc()&nbsp; &nbsp; &nbsp; &nbsp; time.Sleep(time.Duration(rand.Intn(3)) * time.Millisecond)&nbsp; &nbsp; }&nbsp; &nbsp; counter.Cancel()&nbsp; &nbsp; wg.Done()}

Helenr

为了避免“可被比赛检测器检测到”的比赛:正如@caveman 回答的那样,您遇到的问题与wg.Done() / wg.Wait()指令排序中的问题有关,并且您不使用 anatomic.Load()来访问counter.counter.对于这种竞争条件,您的方法是“安全的”。为了避免比赛,如“不要为计数器达到不连贯的状态”:你确实有一个问题,(*)因为你的方法运行了几个连续的指令来检查和更新对象(例如 : if condition { update_fields }),并且你没有同步机制来检查condition应用时是否仍然正确update_fields。通过将您的incrementor功能更改为:func incrementor(wg *sync.WaitGroup) {&nbsp; &nbsp; for i := 0; i < 20000; i++ {&nbsp; &nbsp; &nbsp; &nbsp; counter.Inc()&nbsp; &nbsp; }&nbsp; &nbsp; counter.Cancel()&nbsp; &nbsp; wg.Done()}并多次运行您的程序,您应该能够看到“Final Counter:”并不总是以 0(操场)结尾。这是如何发生这种情况的说明:假设 goroutine 1 执行counter.Cancel()而 goroutine 2 执行counter.Inc()可能会发生以下执行顺序:&nbsp; &nbsp;goroutine 1&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;goroutine 2&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 1. if c.isFinished() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return fmt.Errorf("counter is finished")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}2. if !c.isFinished() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;3.&nbsp; &nbsp; &nbsp;atomic.StoreUint32(&c.finished, 1)&nbsp; &nbsp; &nbsp; &nbsp;4.&nbsp; &nbsp; &nbsp;atomic.StoreUint64(&c.counter, 0)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 5. atomic.AddUint64(&c.counter, 1)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 6. return nil中的c.isFinished()指令.Inc()可能在执行之前发生, .Cancel()并且atomic.AddUint64(&c.counter, 1)可能在将计数器重置为零之后发生。 .Cancel()为了避免这种竞争,您需要选择一种同步inspect + update指令的方式。一种常见的方法是使用互斥锁:type Counter struct {&nbsp; &nbsp; counter&nbsp; uint64&nbsp; &nbsp; finished uint32&nbsp; &nbsp; m&nbsp; &nbsp; &nbsp; &nbsp; sync.Mutex}// Inc increments the counter by onefunc (c *Counter) Inc() error {&nbsp; &nbsp; c.m.Lock()&nbsp; &nbsp; defer c.m.Unlock()&nbsp; &nbsp; if c.finished != 0 {&nbsp; &nbsp; &nbsp; &nbsp; return fmt.Errorf("counter is finished")&nbsp; &nbsp; }&nbsp; &nbsp; c.counter++&nbsp; &nbsp; return nil}// Dec decrements the counter by one, but prevents the counter from going to zerofunc (c *Counter) Dec() {&nbsp; &nbsp; c.m.Lock()&nbsp; &nbsp; defer c.m.Unlock()&nbsp; &nbsp; // prevent overflow&nbsp; &nbsp; if c.counter > 0 {&nbsp; &nbsp; &nbsp; &nbsp; c.counter--&nbsp; &nbsp; }}// Cancel sets the finished flag, and sets counter to zerofunc (c *Counter) Cancel() {&nbsp; &nbsp; c.m.Lock()&nbsp; &nbsp; defer c.m.Unlock()&nbsp; &nbsp; if c.finished == 0 {&nbsp; &nbsp; &nbsp; &nbsp; c.finished = 1&nbsp; &nbsp; &nbsp; &nbsp; c.counter = 0&nbsp; &nbsp; }}
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go