Richard's Blog

Golang中的单例模式

字数统计: 447阅读时长: 1 min
2019/05/05 Share
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
"sync"
)

type singleton map[string]string

var (
once sync.Once

instance singleton
)

func New() singleton {
once.Do(func() {
instance = make(singleton)
})

return instance
}

Golang的单例模式核心在于sync.Once构造体中的Do(f func())方法。官方文档对Do(f func())的说明是Do方法只会在sync.Once对象第一次调用Do方法时真正执行f函数,多次调用Do方法时,即使f函数已经被改变也不会再次调用f函数。想多次调用必须创建新的sync.Once对象。

代码中其实有一个可能是新旧golang版本差异的问题,在我看的golang书籍和文章中,有的地方会写到当一个函数的receiver为类型的指针时,在调用该函数时必须使用receiver类型指针变量调用,并给出一个值变量与指针变量可以调用什么类型的receiver函数的表格,使用值变量调用指针receiver函数会被编译器提示错误。但是经我测试在go1.12.4版本已经没有这样的要求,如下面源代码所示,Do函数的receiver为Once的指针,但是声明sync.Once值变量同样可以调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// sync库源码
type Once struct {
m Mutex
done uint32
}

func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 1 {
return
}
// Slow-path.
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}

OnceDo(f func())的源码可以看到Once结构体有一个done属性,当第一次执行时,Once对象的done属性在创建时被初始化为初始,即为0。当第一次执行Do时会同步执行f函数并把done属性通过atomic库执行原子操作设为1,此后只要done属性为1,则不执行f函数。

CATALOG