Golang Sync.Once 的探究
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 接收一个函数作为参数,该函数不接受任何参数,不返回任何参数。具体做什么由使用方决定,错误处理也由使用方控制,对函数初始化的结果也由使用方进行保存。 ...