那一天晚上,我闭上眼,不知什么时候做了一个梦,梦里遇到了一些人,也不知过了多久,在睁开眼的时候,梦已经醒了,才发现那些人已经不会在生活中遇到了。在梦里,我找到了自己失去的东西,后来才发现,自己有过的东西都会以某种方式离开自己,无论是自己愿意的还是不愿意的。即使自己不舍得,慢慢的随着时间的远去,自己能做的最多的也就是远远的看着它。
生活如果是一本书,那我们在读的时候,难免会看到很多重复的内容,重复的生活细节使得我们也会去寻找那些新鲜的,甚至是一次性的东西。在追求的过程中我们认识到生活在自己定的一些目标下,变得不那么单调无趣。比如我们会在本子上写下很多的愿望,去爬一次长城,与陌生人进行交流,骑车去西藏,约一个陌生人看一场电影。
在某个时候,我们会回忆起对那些愿望做的尝试,这种回忆会让人有再次体验的愿望,或者会感慨那是自己人生中唯一的经历。很奇怪,有些东西如果不经常重复,我们对它的记忆会变得越来越模糊,而有的东西,只要发生一次,就会永远记得。
后来想想,这样也许是一种平衡,在现实中存在很短暂的,我们通过记忆来延续弥补现实中的那种短暂。
在go里面,如何实现一个事情,始终只执行一次呢,只执行一次的目的还是在有可能多次执行的情况下。程序里面的循环,往往是用来重复的执行同一个动作。检验一个动作只会执行一次,也可以通过循环的方式来检验。
package main
import (
"fmt"
"sync"
)
func print() {
fmt.Println("just once")
}
func main() {
num := 5
once := sync.Once{}
for i := 0; i < num; i++ {
once.Do(print)
}
}
上面虽然循环了多次去执行print,结果是指打印了一次。
Once的结构体如下
type Once struct {
// done indicates whether the action has been performed.
// It is first in the struct because it is used in the hot path.
// The hot path is inlined at every call site.
// Placing done first allows more compact instructions on some architectures (amd64/386),
// and fewer instructions (to calculate offset) on other architectures.
done uint32
m Mutex
}
我们看到了之前讲过的Mutex,也就是锁。
func (o *Once) Do(f func()) {
// Note: Here is an incorrect implementation of Do:
//
// if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
// f()
// }
//
// Do guarantees that when it returns, f has finished.
// This implementation would not implement that guarantee:
// given two simultaneous calls, the winner of the cas would
// call f, and the second would return immediately, without
// waiting for the first's call to f to complete.
// This is why the slow path falls back to a mutex, and why
// the atomic.StoreUint32 must be delayed until after f returns.
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()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
为了只让函数执行一次,在正在执行之前,加了一把锁,表示多个协程来执行的时候,只能有一个协程在去执行这个函数,并且在执行完成之后,会通过原子操作去修改done这个状态,表示已经执行一次了,下一次进来的协程看到这个状态是已经执行了,就不会去再次执行。
可以看的出,Do函数执行的函数是一个没有参数的函数,也没有返回值。主要是用在一些初始化的地方,比如在程序里面需要初始化一些配置的参数,程序才可以正常运行。
注意 因为有锁的原因,所以在f函数里面如果去调用Do的话就会发生死锁,相当于里面的协程再次获取另一个协程获得的锁。