Go并发编程—sync包之Once

概述

Once可以让一个动过只发生一次,不管该动作在多少个协程中执行。这种操作可以用于在多线程编程中只允许运行一次的代码块,或初始化代码块中。

函数介绍

func (o *Once) Do(f func())

Once结构的实现

// Once is an object that will perform exactly one action.
type Once struct {
    m    Mutex
    done uint32
}

// Do函数的实现源码
func (o *Once) Do(f func()) {
    // 若已经为1了,说明已经执行了一次,直接返回,不在执行
    if atomic.LoadUint32(&o.done) == 1 {
        return
    }

    // Slow-path.
    // 加锁,并设置为1
    o.m.Lock()
    defer o.m.Unlock()
    if o.done == 0 {
        defer atomic.StoreUint32(&o.done, 1)
        f()
    }
}

Once的使用

func TestSyncOnce() {
    var count int
    increment := func() {
        count++
    }

    var once sync.Once
    var increments sync.WaitGroup

    increments.Add(100)

    for i := 0; i < 100; i++ {
        go func() {
            defer increments.Done()
            once.Do(increment)
        }()
    }

    increments.Wait()
    fmt.Printf("Count is %d\n", count)
}

输出如下:

Count is 1

说明:从Once的实现可以看出,当执行一次后,再执行时将不会在执行了。

由于Once而产生的死锁

var onceA, onceB sync.Once
var initB func()
initA := func() { onceB.Do(initB) }
initB = func() { onceA.Do(initA) } [1]
onceA.Do(initA) [2]

死锁原因分析:

直到[2]返回后[1]才会被执行,若从加锁的序列来看,如下:

lockA
locakB
lockA // 尝试加锁,但不会成功因此阻塞

总结

sync包的Once类型,只会执行一次,这为在多线程的情况下进行初始化,或需要执行一次的工作提供了遍历。但在使用时需要注意由于Once的相互嵌套而引起的死锁问题。

猜你喜欢

转载自blog.csdn.net/zg_hover/article/details/81256865