介绍
sync.Once 是 Go 官方自带的标准库,实现了 exactly once 的功能。通过使用 sync.Once 我们可以很方便地实现单例模式,确保对象只被初始化一次。
首先看一个 sync.Once 的 Go 官方例子,源码链接在这里:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
var once sync.Once
onceBody := func() {
fmt.Println("Only once")
}
done := make(chan bool)
for i := 0; i < 10; i++ {
go func() {
once.Do(onceBody)
done <- true
}()
}
for i := 0; i < 10; i++ {
<-done
}
|
运行程序,输出为:
可见,即便我们并发执行了 10 个协程,onceBody
方法依然只会执行一次,这是如何做到的的呢?
源码走读
Go 语言版本:1.14
源码路径:src/sync/once.go
sync.Once 通过一个锁变量和原子变量保障最多执行一次,下面开始撸代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
package sync
import (
"sync/atomic"
)
// Once 对象结构
type Once struct {
// done 标识被执行的状态:0 为默认值,表示还未执行;1 表示已被执行
done uint32
// m 为互斥锁,控制着临界值的进入,保证同一时间点最多有一个 func 在执行
m Mutex
}
// 对外可调用方法,
func (o *Once) Do(f func()) {
// 原子获取 done 状态只有是 0 状态才允许执行
if atomic.LoadUint32(&o.done) == 0 {
o.doSlow(f)
}
}
// 获取锁,并执行 f
func (o *Once) doSlow(f func()) {
// 获取互斥锁,避免并发问题
o.m.Lock()
defer o.m.Unlock()
// 再次检查是否已经执行完
if o.done == 0 {
// 执行 f 函数后将 done 设置为 1,哪怕 f 发生 panic 恐慌
defer atomic.StoreUint32(&o.done, 1)
//开始执行
f()
}
}
|
短短 20 行左右的代码,隐藏的信息量还是挺大的,这里还有几个知识点需要注意:
- f 函数是同步执行的,也就是说可能存在阻塞问题
- f 函数里面发生 panic,仍会被标识为已完成