Go微服务实战
上QQ阅读APP看书,第一时间看更新

8.2.3 sync.Once

编程时可以使用包的init方法初始化一些变量,但是有些变量可能是程序运行很久以后才会用到,甚至有时候自始至终也不会用到某些变量。那么有什么方法可以在第一次用到某变量时再进行初始化呢?

sync.Once就提供了延迟初始化的功能。其结构和方法如下:


type Once struct {//结构体内的变量都是包外不可见的
    m Mutex
    done uint32
}

func (o *Once) Do(f func())

结构体内的done用来记录执行次数,用m来保证同一时刻只有一个goroutine在执行Do方法。在使用该结构体的时候先定义Once型变量:


var once  Once

然后通过定义的变量向Do方法传入函数,以此作为参数:


once.Do(func() {f()})

即便是多次调用once.Do(f),f也仅仅执行一次。

Once可以解决Mutex和RWMutex不方便解决的问题。如果每次都用Mutex去判断变量是否已经初始化,那么会让所有的goroutine在这一步都是互斥的,而且永远要进行这个操作。RWMutex虽然略好一些,但是会增加系统消耗。Once则可以非常好地解决这个问题,下面来看一个示例:


book/ch08/8.2/once/once.go
1. package main
2.
3. import (
4.     "fmt"
5.     "sync"
6. )
7.
8. func main() {
9.     var wg sync.WaitGroup
10.     var once sync.Once
11.     for i:=0;i<10;i++{
12.         wg.Add(1)
13.         go func() {
14.             defer wg.Done()
15.             once.Do(onlyOnce)
16.         }()
17.     }
18.     wg.Wait()
19. }
20.
21. func onlyOnce()  {
22.     fmt.Println("only once")
23. }

这是一个简单的示例,本例希望让第一个用到onlyOnce函数的goroutine来调用该函数,而后面的goroutine就不需要再调用了。所以,本例使用了sync.Once的Do方法调用onlyOnce方法。示例代码中虽然启动了10个goroutine,但是执行完成会发现onlyOnce函数仅打印一次。

sync.Once是非常好的延迟初始化的方式,延迟初始化可以提高系统启动的速度,也可以保证只初始化一次。