sync.Once 源码解析

sync.Once

sync.Once

因为Once实在是太常用了, 所以今天就对Once的源代码做一个简单的分析

package sync

import (
  "sync/atomic"
)
type Once struct {
  done uint32 // 标识是否已运行
  m    Mutex
}

func (o *Once) Do(f func()) {
  // 如果为运行过, 尝试运行
  if atomic.LoadUint32(&o.done) == 0 {
    // 这里会有使用到互斥锁, 所以会有`slow`
    o.doSlow(f)
  }
}

func (o *Once) doSlow(f func()) {
  // 上锁 / 释放锁
  o.m.Lock()
  defer o.m.Unlock()
  // double check, 另外一个考虑是, 在过个goroutine都尝试doSlow的时候, 能够让第二个之后的goroutine不再走atomic.Load
  if o.done == 0 {
    // 在执行完`f`后, 设置标识位
    defer atomic.StoreUint32(&o.done, 1)
    f()
  }
}

分析

Once的设计永远都值得我们学习, 简单而且精准

我们首先来分析一下Once的作用: 传入func, 保证同样的func只执行一次

很容易想到的一点是用一个flag来标记, 如果为true表示已经执行过, 为false表示未执行过

恭喜, 思路是正确的, 而实现也正是如此.

为什么在load以及store时使用atomic?

我个人认为这里是否必须使用atomic做flag值得考虑, 因为在double-check之后, 我们获取了lock, 必定不会再有竞争情况, 所以此时可以直接读值并判断

但是在load以及set时, 使用atomic可以保证不会出现cpu cache导致某些极端情况下出现重复获取的情况.

总结

整体来说Once的实现是很简单的, 完全不复杂, 只涉及到简单的一些操作和思想

但是其中对于atmoic的使用, 我建议再仔细的阅读.

但是总的来说只有一句话: 如果是并发条件下, 就用atmoic. 如果不存在并发了, 就可以不使用atmic. 在mutex作用域中, 就不需要atmoic

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容