Go單例模式與Once源碼實現(xiàn)
單例實現(xiàn)
type singleton struct{}
var (
instance *singleton
initialized uint32
mu sync.Mutex
)
func Instance() *singleton {
if atomic.LoadUint32(&initialized) == 1 {
return instance
}
mu.Lock()
defer mu.Unlock()
if instance == nil {
defer atomic.StoreUint32(&initialized, 1)
instance = &singleton{}
}
return instance
}其中通用的代碼提取出來,就成了標準庫中sync.Once的實現(xiàn):
type Once struct {
done uint32
m sync.Mutex
}
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 0 {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
}于是,使用sync.Once重新實現(xiàn)單例模式
var (
instance2 *singleton
once sync.Once
)
func Instance2() *singleton {
once.Do(func() {
instance2 = &singleton{}
})
return instance2
}sync.Once源碼分析
1. lock并不會同步值
在lock和unlock之間修改值,并不會保證對其他協(xié)程是可見的,除非使用相同的Mutex加鎖,想要同步值必須使用atomic;
lock可以通過串行化,使得兩個協(xié)程的操作存在happen-before關(guān)系,從而是的操作可見
happen-before原則定義如下:
如果一個操作happens-before(之前發(fā)生)另一個操作,那么第一個操作的執(zhí)行結(jié)果將對第二個操作可見,而且第一個操作的執(zhí)行順序排在第二個操作之前。
兩個操作之間存在happens-before關(guān)系,并不意味著一定要按照happens-before原則制定的順序來執(zhí)行。如果重排序之后的執(zhí)行結(jié)果與按照happens-before關(guān)系來執(zhí)行的結(jié)果一致,那么這種重排序并不非法。
2. Do執(zhí)行一次
當?shù)谝淮螆?zhí)行完Do之后,done設(shè)置成1,后面執(zhí)行Do會直接跳過
3. Once執(zhí)行Do后不準copy
A Once must not be copied after first use.
sync.Once執(zhí)行完Do后done已經(jīng)設(shè)置成1了,copy出來的once執(zhí)行Do會直接跳過
4. Do并發(fā)時阻塞
當兩個或者多個協(xié)程同時調(diào)用Do時,先調(diào)用的協(xié)程執(zhí)行,后面的協(xié)程會阻塞;
解釋:以單例使用once的實現(xiàn)說明,兩個協(xié)程同時調(diào)用Instance2(),先調(diào)用的協(xié)程執(zhí)行創(chuàng)建并拿到返回值,后調(diào)用的阻塞,
? 等到先調(diào)用的完成后再拿到返回值;
意義:這樣的好處是防止后調(diào)用的協(xié)程拿到的是nil
源碼說明:上面第二段代碼13行使用defer,要等f()結(jié)束才會把done設(shè)置成1;其他協(xié)程并發(fā)調(diào)用Do時,done==0,
? 然后請求m.Lock()形成阻塞
5. Do遞歸死鎖
如果Do中的方法調(diào)用當前once的Do會造成死鎖,原因參考上面一點(sync.Mutex.Lock()時不可重入鎖)
參考
- 《Go語言高級編程》
- Go1.16源碼

