今天小编给大家分享一下Golang sync.Once怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。
1. 定位
Once is an object that will perform exactly one action.
sync.Once2. 对外接口
Do(f func())// Do calls the function f if and only if Do is being called for the
// first time for this instance of Once. In other words, given
// var once Once
// if once.Do(f) is called multiple times, only the first call will invoke f,
// even if f has a different value in each invocation. A new instance of
// Once is required for each function to execute.
//
// Do is intended for initialization that must be run exactly once. Since f
// is niladic, it may be necessary to use a function literal to capture the
// arguments to a function to be invoked by Do:
// config.once.Do(func() { config.init(filename) })
//
// Because no call to Do returns until the one call to f returns, if f causes
// Do to be called, it will deadlock.
//
// If f panics, Do considers it to have returned; future calls of Do return
// without calling f.
//
func (o *Once) Do(f func())结合注释,我们来看看 Do 方法有哪些需要注意的:
DofDofDofconfig.once.Do(func() { config.init(filename) })fDofunc main() {
var once sync.Once
once.Do(func() {
once.Do(func() {
fmt.Println("hello kenmawr.")
})
})
}fDofDof3. 实战用法
sync.Once 的场景很多,但万变不离其宗的落脚点在于:任何只希望执行一次的操作。
基于此,我们可以发现很多具体的应用场景落地,比如某个资源的清理,全局变量的初始化,单例模式等,它们本质都是一样的。这里简单列几个,大家可以直接参考代码熟悉。
3.1 初始化
init()init()sync.Once我们来看 Golang 官方的 html 库中的一个例子,我们经常使用的转义字符串函数
func UnescapeString(s string) stringpopulateMapsOnceentityentitymapinit()var populateMapsOnce sync.Once
var entity map[string]rune
func populateMaps() {
entity = map[string]rune{
"AElig;": '\U000000C6',
"AMP;": '\U00000026',
"Aacute;": '\U000000C1',
"Abreve;": '\U00000102',
"Acirc;": '\U000000C2',
// 省略后续键值对
}
}
func UnescapeString(s string) string {
populateMapsOnce.Do(populateMaps)
i := strings.IndexByte(s, '&')
if i < 0 {
return s
}
// 省略后续的实现
...
}3.2 单例模式
Getteronce.DoGetterpackage main
import (
"fmt"
"sync"
)
type Singleton struct{}
var singleton *Singleton
var once sync.Once
func GetSingletonObj() *Singleton {
once.Do(func() {
fmt.Println("Create Obj")
singleton = new(Singleton)
})
return singleton
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
obj := GetSingletonObj()
fmt.Printf("%p\n", obj)
}()
}
wg.Wait()
}
/*--------- 输出 -----------
Create Obj
0x119f428
0x119f428
0x119f428
0x119f428
0x119f428
**/3.3 关闭channel
一个channel如果已经被关闭,再去关闭的话会 panic,此时就可以应用 sync.Once 来帮忙。
type T int
type MyChannel struct {
c chan T
once sync.Once
}
func (m *MyChannel) SafeClose() {
// 保证只关闭一次channel
m.once.Do(func() {
close(m.c)
})
}4. 原理
在 sync 的源码包中,Once 的定义是一个 struct,所有定义和实现去掉注释后不过 30行,我们直接上源码来分析:
package sync
import (
"sync/atomic"
)
// 一个 Once 实例在使用之后不能被拷贝继续使用
type Once struct {
done uint32 // done 表明了动作是否已经执行
m Mutex
}
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 0 {
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}这里有两个非常巧妙的设计值得学习,我们参照注释来看一下:
结构体字段顺序对速度的影响 我们来看一下带注释的 Once 结构定义
type Once struct {
// done indicates whether the action has been performed.
// It is first in the struct because it is used in the hot path.
// The hot path is inlined at every call site.
// Placing done first allows more compact instructions on some architectures (amd64/386),
// and fewer instructions (to calculate offset) on other architectures.
done uint32
m Mutex
}o.donedone为何不使用 CAS 来达到执行一次的效果
atomic.CompareAndSwapUint32Dofunc (o *OnceA) Do(f func()) {
if !atomic.CompareAndSwapUint32(&o.done, 0, 1) {
return
}
f()
}ffDoonce.Dof对此,sync.Once 官方给出的解法是:
Slow path falls back to a mutex, and the atomic.StoreUint32 must be delayed until after f returns.
doSlow()func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 0 {
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}atomic.LoadUint32fo.doSlow(f)doSlowsync.Mutexo.m.Lock()ffdefer atomic.StoreUint32(&o.done, 1)f()5. 避坑
DodoneDofDo以上就是“Golang sync.Once怎么使用”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注亿速云行业资讯频道。