Go猜想录
大道至简,悟者天成
sync.Once 源码分析

Once

sync.Once 一般就是用来确保某个动作至多执行一次。

普遍用于初始化资源,单例模式。

type MyBiz struct {
	once sync.Once
}

// 只执行一次
func (m *MyBiz) Init() {
	m.once.Do(func() {})
}
type singleton struct{}

func (s *singleton) Single() {
	fmt.Println("I am single")
}

var inst *singleton
var instOnce sync.Once

func GetSingleInstance() *singleton {
	instOnce.Do(func() {
		inst = &singleton{}
	})
	return inst
}

注意 receiver 要使用指针类型,否则每次调用 Close 方法时都会值拷贝,生成一个新的 sync.Once 对象。

type OnceClose struct {
	close sync.Once
}

func (o *OnceClose) Close() error {
	o.close.Do(func() {
		fmt.Println("close")
	})
	return nil
}

Once 例子

Beego 用 Once 来初始化 Web 模块

initBeforeHTTPRun 可能会在多个地方被调用,所以需要使用 Once 来确保只会执行一次。

从 TODO 标记也可以看出来,在一些情况下, Once 可能暗示着代码可以放到包初始化方法 init 里面调用。

var initHttpOnce sync.Once

// TODO move to module init function
func initBeforeHTTPRun() {
	initHttpOnce.Do(func() {
		// init hooks
		AddAPPStartHook(
			registerMime,
			registerDefaultErrorHandler,
			registerSession,
			registerTemplate,
			registerAdmin,
			registerGzip,
			// registerCommentRouter,
		)

		for _, hk := range hooks {
			if err := hk(); err != nil {
				panic(err)
			}
		}
	})
}

Once 重置

sync.Once 没有重置功能,所以可以通过 ponce *sync.Once 并使用 ponce = new(sync.Once) 进行重置。之所以没有设计重置功能,是因为这违背了 sync.Once 的初衷,它的目的是确保操作只执行一次。


知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。