Once是sync包里的一个结构体类型,Once可以在并发场景下让某个操作只执行一次,比如设计模式里的单例只创建一个实例,比如只加载一次配置文件,比如对同一个channel只关闭一次(对一个已经close的channel再次close会引发panic)等。
定义如下:
type Once struct {
// some fields
}
这个结构体只有1个方法Do,参数是要执行的函数。(注意:参数是函数类型,而不是函数的返回值,所以只需要把函数名作为参数给到Do即可)
可以看到Do方法的参数f这个函数类型没有参数,所以如果要执行的函数f需要传递参数就要结合Go的闭包来使用。
func(o *Once) Do(f func())
参考下面的例子,print函数通过Once执行,只会执行1次
package main
import (
"fmt"
"sync"
)
func print() {
fmt.Println("test once")
}
func main() {
var wg sync.WaitGroup
var once sync.Once
size := 10
wg.Add(size)
/*启用size个goroutine,每个goroutine都调用once.Do(print)
最终print只会执行一次
*/
for i:=0; i<size; i++ {
go func() {
defer wg.Done()
once.Do(print)
}()
}
/*等待所有goroutine执行完成*/
wg.Wait()
fmt.Println("end")
}
package main
import (
"fmt"
"sync"
)
type Singleton struct {
member int
}
var instance *Singleton
var once sync.Once
func getInstance() *Singleton {
/*
通过sync.Once实现单例,只会生成一个Singleton实例
*/
once.Do(func() {
fmt.Println("once")
instance = &Singleton{}
instance.member = 100
})
fmt.Println(instance.member)
return instance
}
func main() {
var wg sync.WaitGroup
size := 10
wg.Add(size)
/*
多个goroutine同时去获取Singelton实例
*/
for i:=0; i<size; i++ {
go func() {
defer wg.Done()
instance = getInstance()
}()
}
wg.Wait()
fmt.Println("end")
}
-
Once变量作为函数参数传递时,只能传指针,不能传值。传值给函数A的话,对于函数A而言,参数列表里的once形参会是一个新生成的once局部变量,和外部传入的once实参不一样。
package main import ( "fmt" "sync" ) func test() { fmt.Println("test once") } func print(once *sync.Once) { once.Do(test) } func main() { var wg sync.WaitGroup var once sync.Once size := 10 wg.Add(size) /*启用size个goroutine,每个goroutine都调用once.Do(print) 最终print只会执行一次 */ for i:=0; i<size; i++ { go func() { defer wg.Done() print(&once) }() } /*等待所有goroutine执行完成*/ wg.Wait() fmt.Println("end") }
-
如果once.Do(f)方法调用的函数f发生了panic,那Do也会认为函数f已经return了。
-
如果多个goroutine执行了都去调用once.Do(f),只有某次的函数f调用返回了,所有Do方法调用才会返回,否则Do方法会一直阻塞等待。如果在f里继续调用同一个once变量的Do方法,就会死锁了,因为Do在等待f返回,f又在等待Do返回。