Once

sync.Once 是 Go 语言标准库 sync 包中的一个类型,用于确保某个操作在并发环境下只执行一次。它通常用于初始化资源或执行一次性设置操作,避免重复执行带来的问题。

核心功能

sync.Once 的核心方法是 Do,其签名如下:

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

Do 方法接收一个函数 f 作为参数,并保证该函数只执行一次,即使多次调用 Do 方法也是如此。

使用场景

  • 单例模式:确保全局唯一实例的创建。
  • 初始化操作:如数据库连接、配置加载等,确保只执行一次。
  • 资源释放:确保资源只释放一次,避免重复释放。

内部实现

sync.Once 内部通过一个 uint32 类型的标志位和一个互斥锁 m 来实现。Do 方法首先检查标志位,如果为 0,则执行传入的函数并将标志位置为 1,确保函数只执行一次。

 1package sync
 2
 3import (
 4	"sync/atomic"
 5)
 6
 7type Once struct {
 8	done atomic.Uint32
 9	m    Mutex
10}
11
12func (o *Once) Do(f func()) {
13	if o.done.Load() == 0 {
14		o.doSlow(f)
15	}
16}
17
18func (o *Once) doSlow(f func()) {
19	o.m.Lock()
20	defer o.m.Unlock()
21	if o.done.Load() == 0 {
22		defer o.done.Store(1)
23		f()
24	}
25}

注意事项

sync.Once 是不可重用的,即一旦 Do 方法执行了传入的函数,后续调用 Do 方法将不会再次执行该函数。可以额外调整,使得当传入函数返回错误时,再次调用 Do 方法可以再次执行传入的函数。

 1package zonce
 2
 3import (
 4	"sync"
 5	"sync/atomic"
 6)
 7
 8type Once struct {
 9    done uint32
10	sync.Mutex
11}
12// 传入的函数f有返回值error,如果初始化失败,需要返回失败的error
13// Do方法会把这个error返回给调用者
14func (o *Once) Do(f func() error) error {
15	if atomic.LoadUint32(&o.done) == 1 { //fast path
16		return nil
17	}
18	return o.doSlow(f)
19}
20
21func (o *Once) doSlow(f func() error) error {
22	o.Lock()
23	defer o.Unlock()
24	var err error
25	if o.done == 0 {
26        if err := f(); err == nil {
27            // 初始化成功才将标记置为已初始化
28			atomic.StoreUint32(&o.done, 1)
29		}s
30	}
31	return err
32}

参考

  1. What does “hot path” mean in the context of sync.Once?