共计 2638 个字符,预计需要花费 7 分钟才能阅读完成。
点击上方蓝色“迈莫 coding”,抉择“设为星标”
目录
=====================================
- 什么是 sync.Once
- 如何应用 sync.Once
- 源码剖析
什么是 sync.Once
===========================================
Once 能够用来执行且仅仅执行一次动作,经常用于单例对象的初始化场景。
Once 经常用来初始化单例资源,或者并发拜访只需初始化一次的共享资源,或者在测试的时候初始化一次测试资源。
sync.Once 只裸露了一个办法 Do,你能够屡次调用 Do 办法,然而只有第一次调用 Do 办法时 f 参数才会执行,这里的 f 是一个无参数无返回值的函数。
如何应用 sync.Once
=============================================
就拿我当初负责的一个我的项目来说,因为我的项目的配置是挂在第三方平台上,所以在我的项目启动时须要获取资源配置,因为须要一个办法来保障配置仅此只获取一次,因而,咱们思考应用 sync.Once
来获取资源。这样的话,能够避免在其余中央调用获取资源办法,该办法仅此执行一次。
上面我简略写个 Demo 来演示一个 sync.Once 如何应用
package main
import (
"fmt"
"sync"
)
var once sync.Once
var con string
func 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.go
type 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」第一工夫浏览。每天分享优质文章、大厂教训、大厂面经,助力面试,是每个程序员值得关注的平台。
-
你点的每个赞,我都认真当成了喜爱