Once介绍
在项目开发中,我们经常会需要调用其他服务或初始化一些资源。此时就需要用到单例模式,通过一个全局变量,在资源首次被使用时服务会去初始化它,其他地方需要使用资源时就用这个全局变量。但是在多线程环境下,这个全局变量可能会被多个线程同时初始化,这时就需要用到sync.Once。
sync.Once能确保对应的变量/资源只被初始化一次,节约内存资源,提高性能。本文就来介绍下它的使用与实现。
基本使用
终端运行go doc看下sync.Once的文档:
|
|
sync.Once对外暴露了一个Do方法,该方法接收一个函数作为参数,这个函数就是业务上仅需要执行一次的逻辑(如上文说的初始化某些资源)。Do方法会确保这个函数只会被执行一次。
|
|
上面的代码中,我们定义了一个hello函数,然后在main函数中,我们创建了一个sync.Once实例once,然后启动了10个协程,每个协程都会调用once.Do(hello)。由于sync.Once的特性,hello函数只会被执行一次,所以最终只会打印一次Hello。关于sync.WaitGroup的使用可以参考我上一篇博客,这里不做介绍。
通过
sync.Once,我们可以确保某个函数只会被执行一次,从而就可以避免重复初始化资源,提高性能。
源码分析
本文代码基于golang 1.22
sync.Once的定义如下:
|
|
有两个字段,done和m。done是一个atomic.Uint32类型的变量,用来标记是否已经执行过Do方法。m是一个Mutex类型的变量,用来保护done字段。
|
|
Do方法首先会判断done字段是否为0,如果为0,说明还没有执行过Do方法,那么就调用doSlow方法(通过外联的方式,因为大部分情况下f已经被执行了,不会走到doSlow方法)。doSlow方法中,首先会加锁,接着再判断一下done字段是否为0(双检测机制)。- 执行
f函数,然后将done字段设置为1,表示已经执行过Do方法。
整体实现还是很简单的,通过atomic.Uint32和Mutex来保证Do方法只会被执行一次。