golang源码分析-Once

Once介绍

在项目开发中,我们经常会需要调用其他服务或初始化一些资源。此时就需要用到单例模式,通过一个全局变量,在资源首次被使用时服务会去初始化它,其他地方需要使用资源时就用这个全局变量。但是在多线程环境下,这个全局变量可能会被多个线程同时初始化,这时就需要用到sync.Once

sync.Once能确保对应的变量/资源只被初始化一次,节约内存资源,提高性能。本文就来介绍下它的使用与实现。

基本使用

终端运行go doc看下sync.Once的文档:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ go doc sync.Once
package sync // import "sync"

type Once struct {
	// Has unexported fields.
}
    Once is an object that will perform exactly one action.

    A Once must not be copied after first use.

    In the terminology of the Go memory model, the return from f “synchronizes
    before” the return from any call of once.Do(f).

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

sync.Once对外暴露了一个Do方法,该方法接收一个函数作为参数,这个函数就是业务上仅需要执行一次的逻辑(如上文说的初始化某些资源)。Do方法会确保这个函数只会被执行一次。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func hello() {
	fmt.Println("Hello")
}

func main() {
	once := sync.Once{}
	wg := sync.WaitGroup{}
	wg.Add(10)

	for i := 0; i < 10; i++ {
		go func() {
			defer wg.Done()
			once.Do(hello)
		}()
	}

	wg.Wait()
}

上面的代码中,我们定义了一个hello函数,然后在main函数中,我们创建了一个sync.Once实例once,然后启动了10个协程,每个协程都会调用once.Do(hello)。由于sync.Once的特性,hello函数只会被执行一次,所以最终只会打印一次Hello。关于sync.WaitGroup的使用可以参考我上一篇博客,这里不做介绍。

通过sync.Once,我们可以确保某个函数只会被执行一次,从而就可以避免重复初始化资源,提高性能。

源码分析

本文代码基于golang 1.22

sync.Once的定义如下:

1
2
3
4
5
type Once struct {
	// done indicates whether the action has been performed.
	done atomic.Uint32
	m    Mutex
}

有两个字段,donemdone是一个atomic.Uint32类型的变量,用来标记是否已经执行过Do方法。m是一个Mutex类型的变量,用来保护done字段。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func (o *Once) Do(f func()) {
	if o.done.Load() == 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.Load() == 0 {
		defer o.done.Store(1)
		f()
	}
}
  1. Do方法首先会判断done字段是否为0,如果为0,说明还没有执行过Do方法,那么就调用doSlow方法(通过外联的方式,因为大部分情况下f已经被执行了,不会走到doSlow方法)。
  2. doSlow方法中,首先会加锁,接着再判断一下done字段是否为0(双检测机制)。
  3. 执行f函数,然后将done字段设置为1,表示已经执行过Do方法。

整体实现还是很简单的,通过atomic.Uint32Mutex来保证Do方法只会被执行一次。

updatedupdated2024-07-102024-07-10