本书未发布

Go: sync.Once和sync.Mutex

未匹配的标注

之前问过这个问题,现在准备再次重温下这个。博客:sync.Once和自己加锁有什么区别吗?

// Once is an object that will perform exactly one action.
//
// A Once must not be copied after first use.
type Once struct {
    done uint32
    m    Mutex
}

func (o *Once) Do(f func()) {
    if atomic.LoadUint32(&o.done) == 0 {
  // Outlined slow-path to allow inlining of the fast-path.
  o.doSlow(f)
  }
}

func (o *Once) doSlow(f func()) {
  o.m.Lock()
  defer o.m.Unlock()
  if o.done == 0 {
  defer atomic.StoreUint32(&o.done, 1)
  f()
  }
}

使用方式:

var loadIconsOnce sync.Once
func Icon(name string) string {
    loadIconsOnce.Do(loadIcons)
    return icons[name]
}

使用说明:

  1. sync.Once无需初始化也行, 只要申明类型即可。下面的也行!

    var loadIconsOnce = sync.Once{}
  2. loadIconsOnce.Do(loadIcons) 只会执行一次。对于多协程调用的话,是安全的。

    if atomic.LoadUint32(&o.done) == 0 {
         o.doSlow(f)
     }

    这里比较为什么用原子性比较呢?如果改成普通的比较方法会怎么样?

    if o.done == 0 {
         o.doSlow(f)
     }

在本地将Once拷贝出来,然后改动如下:

func Icon(name string) string {
    go loadIconsOnce.Do(loadIcons)
    go loadIconsOnce.Do(loadIcons)
    go loadIconsOnce.Do(loadIcons)
    return icons[name]
}

func  (o *Once)  Do(f func())  {  
    if atomic.LoadUint32(&o.done)  ==  0  {  
        fmt.Println("atomic")
        o.doSlow(f)
    }
}

输出如下:

=== RUN   TestIcon
atomic
loading icons...
    other_test.go:38:  
atomic
--- PASS: TestIcon (0.00s)
PASS

可见在并发时,if atomic.LoadUint32(&o.done) == 0是不能阻挡并发执行到o.doSlow(f)。接下来很好理解,通过sync.Mutex锁+比较done的值保证单例只会被执行一次。这么做怎么就比直接用sync.Mutex性能好呢?
如下:

var lock sync.Mutex
func Icon(name string) string {
    lock.Lock() //加锁
    if icons == nil {
      loadIcons()
    }
    lock.Unlock()
    return icons[name]
}

每次执行Icon函数都会加锁。但是如果只执行一次呢?如下:

func Init() {
    lock.Lock()
    defer lock.Unlock()
    loadIcons()
}

func Icon(name string) string {
    return icons[name]
}

这样就在项目启动时执行一次Init,然后在执行Icon方法才能获取到值。如果Init还正在初始化中,但是已经在调用Icon呢?那么就会出现问题。

通过这样对比,就能发现sync.Once的好处了:

  1. 方法中使用sync.Once后,无须担心并发多次调用和后续再被调用带来的性能问题。因为无论调多少次只是多了一次done的比较而已。
  2. 被调用的方法保证一定能执行完才会返回,这样保证了初始化的数据一定会初始化完成再被使用。如果只是sync.Mutex无法做到并发时的安全。
  3. 写法上的区别,sync.Once可以在初始化和调用时写在一起。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~