NOTE
2.7 sync.Once
What sync.Once is, singleton usage, its atomic+Mutex implementation, and a modified version that returns an error.
This is a historical learning note and may contain outdated or incomplete understanding.
1. What It Is
Once is an object that guarantees that an action is executed only once.
Put simply, it makes a method execute only once.
2. Usage
2.1. Singleton Pattern
Singleton Pattern.md (related note is not yet published)
type singleton struct {
}
// private
var instance *singleton
var once sync.Once
func TestSync1(t *testing.T) {
instance1 := GetInstance()
instance2 := GetInstance()
fmt.Println(instance1 == instance2)
}
func GetInstance() *singleton {
once.Do(func() {
instance = &singleton{}
})
return instance
}
3. Implementation
Implemented through sync.atomic + sync.Mutex.
type Once struct {
done uint32
m Mutex
}
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 0 {
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()
}
}
-
Why does the
Domethod useatomic.LoadUint32(&o.done) == 0instead of directly usingo.done == 0? Ifo.done == 0is used directly, changes too.donemade bydoSlowmay not be observed in time. -
Why does the
doSlowmethod directly useo.done == 0? It is already locked and is inside the critical section. -
Since
Lockis already used, why not directly useo.done = 1? Why isatomic.StoreUint32(&o.done, 1)still needed?
4. Modification: Return error
// copy from sync.Once, add return error
type Once struct {
done uint32
m sync.Mutex
}
func (o *Once) Do(f func() error) error {
if atomic.LoadUint32(&o.done) == 0 {
return o.doSlow(f)
}
return nil
}
func (o *Once) doSlow(f func() error) error {
o.m.Lock()
defer o.m.Unlock()
var err error
if o.done == 0 {
err = f()
if err == nil {
defer atomic.StoreUint32(&o.done, 1)
}
}
return err
}
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub