Golang的sync包--sync.Once

  1. sync.Once:sync包是golang提供的一个线程安全的同步包,once一次,once提供的Do方法中的f只会被调用一次,简便f出现了panic,再次调用once的Do方法,f也不会被执行

  2. sync.Once使用示例

func main() {
	once := sync.Once{}
	for i := 0; i < 5; i++ {
		once.Do(doSomething)
		time.Sleep(time.Second)
	}
	fmt.Println("end")
}

func doSomething() {
	fmt.Printf("do it, time_stamp:%d\n", time.Now().Unix())
}

输出结果:

do it, time_stamp:1559812858
end

once.Do被执行了5次,但是只有一个输出,也就是doSomething只被执行了一次,这样让我们很好的联想到了单例模式,多次执行只会创建一个对象

  1. 使用sync.Once实现单例模式
func main() {
	//启动多个协程获取对象并且调用对象的打印内存地址的方法
	for i := 0; i < 5; i++ {
		go func() {
			singleton := GetSingleton()
			singleton.PrintAddress()
		}()
	}
	//睡眠一下,避免go程未执行就终止了程序
	time.Sleep(time.Second)
}

type singleton struct {
}

var st *singleton
var once sync.Once

//获取对象实例
func GetSingleton() *singleton {
	if st != nil {
		return st
	}
	once.Do(func() {
		//模拟对象创建耗时操作
		time.Sleep(time.Millisecond*10)
		st = &singleton{}
	})
	return st
}

//打印内存地址
func (s *singleton) PrintAddress() {
	fmt.Printf("%p \n", s)
}

输出结果:

0x59a1c8 
0x59a1c8 
0x59a1c8 
0x59a1c8 
0x59a1c8 

输出结果都是一样的,对象的内存地址都一样,所以对象都是同一个,对象没有被重复创建。使用sync.Once很优雅的实现了单例模式,无需自己进行加锁等操作

4.源码分析

type Once struct {
	m    Mutex  //互斥锁
	done uint32 //f方法的执行标识,0未执行,1已执行
}

func (o *Once) Do(f func()) {
 	//done==1,表示f已经被执行了,直接返回
	if atomic.LoadUint32(&o.done) == 1 {
		return
	}
	
	//加锁
	o.m.Lock()
	//使用defer释放锁
	defer o.m.Unlock()
	
	//再次判断done是否已经执行,done==0表示未执行,双重校验,避免f多次执行
	if o.done == 0 {
		//执行f方法,执行完成后,done置为1,
		//因为使用defer将done置为1,所以即便是f中panic了,done也会被置为1
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}

5.小结
如果我们的代码中需要某部分代码只被执行一次,可以使用sync.once优雅的去实现。比如:程序启动时,初始化配置文件解析到对象当中,抑或着是使用单例模式都可以使用once很好的实现。

你可能感兴趣的:(Golang)