使用

sync.Once 可以控制函数只能被调用一次,不能多次重复调用。

例如如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import (
	"fmt"
	"sync"
)

func main() {
	once := sync.Once{}
	n := 10
	for i := 0; i < n; i++ {
		once.Do(f)
	}
}

func f() {
	fmt.Println("Hello World")
}

虽然在 for 循环中多次执行了 once.Do(f) ,但是最终却只执行了一次f 函数而已。

这是什么原因呢?查看一下 sync.Once 的源码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type Once struct {
    // 执行标记
	done uint32
    // 互斥锁
	m    Mutex
}

func (o *Once) Do(f func()) {
    // 原子操作判断o.done是否为1,若为1,表示f已经执行过,直接返回
	if atomic.LoadUint32(&o.done) == 0 {
		// Outlined slow-path to allow inlining of the fast-path.
		o.doSlow(f)
	}
}

func (o *Once) doSlow(f func()) {
     //加锁,保证互斥访问
	o.m.Lock()
	defer o.m.Unlock()
    //这里需要再次重新判断下,因为 atomic.LoadUint32取出状态值到  o.m.Lock() 之间是有可能存在其它gotoutine改变status的状态值的
	if o.done == 0 {
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}

可以看到 sync.Once 只执行函数一次的原理类似于单例模式中的双重检查锁,因此 sync.Once 最好的应用场景就是在单例模式中使用它。

如下就可以每次调用 singleton.Instance() 都是返回同一个单例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package singleton

import (
    "fmt"
    "sync"
)

// 单例结构体
type object struct {
}

var once sync.Once
var obj *object //单例指针

//公开方法 外包调用
func Instance() *object {
    once.Do(getObj)
    return obj
}

func getObj() {
    if obj == nil {
        obj = new(object)
        //可以做其他初始化事件
    }
}