点击上方蓝色“迈莫coding”,抉择“设为星标”
目录
=====================================
- 什么是sync.Once
- 如何应用sync.Once
- 源码剖析
什么是sync.Once
===========================================
Once 能够用来执行且仅仅执行一次动作,经常用于单例对象的初始化场景。
Once 经常用来初始化单例资源,或者并发拜访只需初始化一次的共享资源,或者在测试的时候初始化一次测试资源。
sync.Once 只裸露了一个办法 Do,你能够屡次调用 Do 办法,然而只有第一次调用 Do 办法时 f 参数才会执行,这里的 f 是一个无参数无返回值的函数。
如何应用sync.Once
=============================================
就拿我当初负责的一个我的项目来说,因为我的项目的配置是挂在第三方平台上,所以在我的项目启动时须要获取资源配置,因为须要一个办法来保障配置仅此只获取一次,因而,咱们思考应用 sync.Once
来获取资源。这样的话,能够避免在其余中央调用获取资源办法,该办法仅此执行一次。
上面我简略写个Demo来演示一个sync.Once如何应用
package mainimport ( "fmt" "sync")var once sync.Oncevar con stringfunc main() { once.Do(func() { con = "hello Test once.Do" }) fmt.Println(con)}
代码阐明:
代码的话比较简单,就是通过调用Do办法,采纳闭包形式,将字符串("hello Test once.Do")赋值给con,进而打印出值,这就是 sync.Once
的应用,比拟容易上手。
但咱们用一个办法或者框架时,如果不对其一目了然,总有点不太靠谱,感觉心里不虚浮。为此,咱们来聊一聊 sync.Once
的源码实现,让他无处可遁。
源码剖析
===========================
接下来剖析 sync.Do
到底是如何实现的,它存储在包sync下 once.go
文件中,源代码如下:
// sync/once.gotype Once struct { done uint32 // 初始值为0示意还未执行过,1示意曾经执行过 m Mutex }func (o \*Once) Do(f func()) { // 判断done是否为0,若为0,示意未执行过,调用doSlow()办法初始化 if atomic.LoadUint32(&o.done) == 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() // 采纳双重检测机制 加锁判断done是否为零 if o.done == 0 { // 执行完f()函数后,将done值设置为1 defer atomic.StoreUint32(&o.done, 1) // 执行传入的f()函数 f() }}
接下来会分为两大部分进行剖析,第一部分为 Once
的构造体组成构造,第二局部为 Do
函数的实现原理,我会在代码上加上正文,保障用心浏览完都有播种。
构造体
type Once struct { done uint32 // 初始值为0示意还未执行过,1示意曾经执行过 m Mutex }
首先定义一个struct构造体 Once
,外面存储两个成员变量,别离为 done
和 m
。
done成员变量
- 1示意资源未初始化,须要进一步初始化
- 0示意资源已初始化,无需初始化,间接返回即可
m成员变量
- 为了避免多个goroutine调用
doSlow()
初始化资源时,造成资源屡次初始化,因而采纳Mutex
锁机制来保障有且仅初始化一次
Do
func (o \*Once) Do(f func()) { // 判断done是否为0,若为0,示意未执行过,调用doSlow()办法初始化 if atomic.LoadUint32(&o.done) == 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() // 采纳双重检测机制 加锁判断done是否为零 if o.done == 0 { // 执行完f()函数后,将done值设置为1 defer atomic.StoreUint32(&o.done, 1) // 执行传入的f()函数 f() }}
调用 Do
函数时,首先判断done值是否为0,若为1,示意传入的匿名函数 f()
已执行过,无需再次执行;若为0,示意传入的匿名函数 f()
还未执行过,则调用 doSlow()
函数进行初始化。
在 doSlow()
函数中,若并发的goroutine进入该函数中,为了保障仅有一个goroutine执行 f()
匿名函数。为此,须要加互斥锁保障只有一个goroutine进行初始化,同时采纳了双查看的机制(double-checking),再次判断 o.done 是否为 0,如果为 0,则是第一次执行,执行结束后,就将 o.done 设置为 1,而后开释锁。
即便此时有多个 goroutine 同时进入了 doSlow 办法,因为双查看的机制,后续的 goroutine 会看到 o.done 的值为 1,也不会再次执行 f。
这样既保证了并发的 goroutine 会期待 f 实现,而且还不会屡次执行 f。
分割线
往期举荐
[
【七天从零实现ORM框架】Day01:序言
](http://mp.weixin.qq.com/s?__b...
[
go反射那些事儿|isNil()/isValid()/Call()函数应用
](http://mp.weixin.qq.com/s?__b...
[
go那些事儿|go反射第一弹(TypeOf)
](http://mp.weixin.qq.com/s?__b...
[
go那些事儿|channel应用及其实现原理
](http://mp.weixin.qq.com/s?__b...
go那些事儿|defer必把握常识
文章也会继续更新,能够微信搜寻「 迈莫coding 」第一工夫浏览。每天分享优质文章、大厂教训、大厂面经,助力面试,是每个程序员值得关注的平台。
你点的每个赞,我都认真当成了喜爱