sync.Once的源码十分简单,而且注释十分清楚,直接来看一下。
源码
定义了一个结构体Once
type Once struct { done uint32 m Mutex }
|
对外提供了一个方法Do,Once.Do可以理解成资源初始化,只会执行一次。
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,这里你可能会有个疑问,Do方法里面为何不直接使用cas原子操作呢,那多简洁?
if atomic.CompareAndSwapUint32(&o.done, 0, 1) { f() }
|
其实源码注释里已经有了说明,这样的实现并不合理。
当你有2个并发请求调用Do,这样的实现确实能保证只会调用一次f。但是,假如f的执行需要一段时间,比如初始化数据库连接池,当f执行尚未完成,并发中另一个请求因为没有执行原子操作直接返回了,使用f中初始化的连接池就必然会失败,那么这样的实现显然是不可取的。
所以必须确保f执行完成之后,才能将done置为1。
使用
var one sync.Once fun1 := func() { fmt.Println("do one") }
fun2 := func() { fmt.Println("do two") } one.Do(fun1) one.Do(fun2)
output: do one
|
可以看到只执行了一次,也就是fun1。
其实once很适合应用到单例模式,比如连接数据库,
package db
import ( "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" "github.com/spf13/viper" )
var once sync.Once var db *gorm.DB
func GetDB() *gorm.DB { once.Do(func() { db = openPool() })
return db }
func openPool() *gorm.DB { ... }
|
好了,Once是很常用的,也很适合单例模式使用,源码简单明了,以后在项目中多多使用吧!