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

posted @ 2024-03-28 04:28  pDJJq  阅读(4)  评论(0编辑  收藏  举报