sync.Once
的用法#
在多数情况下,sync.Once
被用于控制变量的初始化,这个变量的读写通常遵循单例模式,满足这三个条件:
- 当且仅当第一次读某个变量时,进行初始化(写操作)
- 变量被初始化过程中,所有读都被阻塞(读操作;当变量初始化完成后,读操作继续进行)
- 变量仅初始化一次,初始化完成后驻留在内存里
实例化一次客户端#
在标准库中不乏有大量 sync.Once
的使用案例,在 strings
包中 replace.go
里实现字符串批量替换功能时,需要预编译生成替换规则,即采用不同的替换算法并创建相关算法实例,因 strings.Replacer
实现是线程安全且支持规则复用,在第一次解析替换规则并创建对应算法实例后,可以并发的进行字符串替换操作,避免多次解析替换规则浪费资源。
先看一下 strings.Replacer
的结构定义:
1
2
3
4
5
6
| // source: strings/replace.go
type Replacer struct {
once sync.Once // guards buildOnce method
r replacer
oldnew []string
}
|
这里定义了 once sync.Once
用来控制 r replacer
替换算法初始化,当我们使用 strings.NewReplacer
创建 strings.Replacer
时,这里采用惰性算法,并没有在这时进行 build
解析替换规则并创建对应算法实例,而是在执行替换时( Replacer.Replace
和 Replacer.WriteString
)进行的, r.once.Do(r.buildOnce)
使用 sync.Once
的 Do
方法保证只有在首次执行时才会执行 buildOnce
方法,而在 buildOnce
中调用 build
解析替换规则并创建对应算法实例,在 buildOnce
中进行赋值。
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
| // source: strings/replace.go
func NewReplacer(oldnew ...string) *Replacer {
if len(oldnew)%2 == 1 {
panic("strings.NewReplacer: odd argument count")
}
return &Replacer{oldnew: append([]string(nil), oldnew...)}
}
func (r *Replacer) buildOnce() {
r.r = r.build()
r.oldnew = nil
}
func (b *Replacer) build() replacer {
....
}
func (r *Replacer) Replace(s string) string {
r.once.Do(r.buildOnce)
return r.r.Replace(s)
}
func (r *Replacer) WriteString(w io.Writer, s string) (n int, err error) {
r.once.Do(r.buildOnce)
return r.r.WriteString(w, s)
}
|
简单来说,once.Do
中的函数只会执行一次,并保证 once.Do
返回时,传入 Do
的函数已经执行完成。多个 goroutine
同时执行 once.Do
的时候,可以保证抢占到 once.Do
执行权的 goroutine
执行完 once.Do
后,其他 goroutine
才能得到返回。
once.Do
接收一个函数作为参数,该函数不接受任何参数,不返回任何参数。具体做什么由使用方决定,错误处理也由使用方控制,对函数初始化的结果也由使用方进行保存。
资源清理退出#
一种错误处理的例子 exec.closeOnce
,exec.closeOnce
保证了重复关闭文件,永远只执行一次,并且总是返回首次关闭产生的错误信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // source: os/exec/exec.go
type closeOnce struct {
*os.File
once sync.Once
err error
}
func (c *closeOnce) Close() error {
c.once.Do(c.close)
return c.err
}
func (c *closeOnce) close() {
c.err = c.File.Close()
}
|
nsq的停止#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // source: apps/nsqd/main.go
type program struct {
once sync.Once
nsqd *nsqd.NSQD
}
func main() {
prg := &program{}
if err := svc.Run(prg, syscall.SIGINT, syscall.SIGTERM); err != nil {
logFatal("%s", err)
}
}
func (p *program) Sweight() error {
p.once.Do(func() {
p.nsqd.Exit()
})
return nil
}
|
常见的单例模式#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| var (
onceHost sync.Once
hostQueue *mq.PubSub
)
func getHostInstance() *mq.PubSub {
onceHost.Do(func() {
var err error
hostQueue, err = mq.NewPubSub(context.Background())
if err != nil {
log.Error(err)
}
})
return hostQueue
}
|
参考链接#