text
In many cases, we may need to control a piece of code to be executed only once, such as doing some initialization operations, such as initializing database connections, etc. For this scenario, go provides us withObject, it ensures that an action is executed only once. Of course we can pass it ourselves.
Mutex
accomplishThe function of , but it is a bit cumbersome, because we not only need to control the lock ourselves, but also use a logo to mark whether it has been executed.
Implementation of Once
Once
The implementation is very simple. As follows, there are only 20 lines of code, but it contains some common processing methods for go concurrency and synchronization.
package sync import ( "sync/atomic" ) type Once struct { done uint32 m Mutex } func (o *Once) Do(f func()) { if atomic.LoadUint32(&) == 0 { (f) } } func (o *Once) doSlow(f func()) { () defer () if == 0 { defer atomic.StoreUint32(&, 1) f() } }
Brief description:
-
done
The field indicates whether the operation has been executed, that is, we pass it toDo
whether the function has been executed. -
Do
The method receives a function parameter, which will be executed only once. -
Once
Internal is throughMutex
to achieve synchronization between different coroutines.
Example of usage
In the following example,(test)
Is executed 3 times, but in the endtest
Only executed once.
package sync import ( "fmt" "sync" "testing" ) var once var a = 0 func test() { a++ } func TestOnce(t *) { var wg (3) for i := 0; i < 3; i++ { go func() { // Will call 3 times, but will only execute once in the end (test) () }() } () (a) // 1 }
Some working mechanisms of Once
-
Once
ofDo
Methods can ensure that multiple goroutines are executed simultaneouslyDo
When the method is taken, the firstDo
Before the execution of the goroutine with the execution rights returns, other goroutines will be blocked.There is only the first one on the call
Do
When the call returns, other goroutines can continue to be executed, and all other goroutines will no longer be executed and passed toDo
function. (If it is an initialization scenario, this can avoid performing other operations before the initialization is completed) - if
occur
panic
When it is passed toDo
The function is still marked as completed. Follow-upDo
The call will not be executed toDo
function parameters. - We can't simply pass
atomic.CompareAndSwapUint32
To decide whether to executef()
, because it cannot be guaranteed when multiple goroutines are executed simultaneouslyf()
Only executed once. soOnce
Used insideMutex
, so as to effectively protect the critical area.
// Error implementation, this cannot guarantee that f is executed only onceif atomic.CompareAndSwapUint32(&amp;, 0, 1) { f() }
-
The function parameters have no parameters. If we need to pass some parameters, we can use them again
f
Make a layer of parcel.
(func() { (filename) })
Once detailed explanation
hotpath
What is said herehotpath
Refers toOnce
The first field indone
:
type Once struct { // hotpath done uint32 m Mutex }
Once
The first field of the structure isdone
, this is becausedone
The visit is much greater thanOnce
Another field inm
, put in the first field, the compiler can do some optimizations, because the address of the structure is actually the address of the first field of the structure, so that it can be accesseddone
When using fields, you do not need to access them through the structure address + offset, which improves performance to a certain extent.
Structural address calculation example:
type person struct { name string age int } func TestStruct(t *) { var p = person{ name: "foo", age: 10, } // The address of p and // 0xc0000100a8, 0xc0000100a8 ("%p, %p\n", &p, &) // Address // 0xc0000100b8 ("%p\n", &) The address of // can also be calculated by: structure address + age field offset. // 0xc0000100b8 (((&p), ())) }
atomic.LoadUint32
func (o *Once) Do(f func()) { if atomic.LoadUint32(&) == 0 { (f) } }
existDo
In the method, it is throughatomic.LoadUint32
To judgedone
Whether it is equal to 0, this is because if used directlydone == 0
In the way, it may lead todoSlow
Insidedone
After setting to 1,Do
It cannot be observed normally in the method. So useatomic.LoadUint32
。
And indoSlow
It can be passed insidedone == 0
To judge, this is becausedoSlow
It has been passed insideMutex
Protected. Unique settingsdone = 1
The place is in the critical area, sodoSlow
Pass insidedone == 0
It's completely no problem to judge.
atomic.StoreUint32
func (o *Once) doSlow(f func()) { () defer () if == 0 { defer atomic.StoreUint32(&, 1) f() } }
existdoSlow
In the method, setdone
For 1, also byatomic.StoreUint32
Set it up. This ensures that it is set updone
After 1, it can be seen by other goroutines in time.
Mutex
doSlow
In the realization, it will eventually be passedMutex
to protect critical areas, throughMutex
Can be achievedf
Only executed once, and other goroutines can be used this timef
execution results. Because other goroutines are on the first timef
Before the call returns, it is blocked and retrieved.Mutex
locked places when they getMutex
When locking, you can continue to execute, but at this timef
Execution has been completed, so when they are retrievedMutex
After locking, nothing was done.
However, their blocking state is released and can continue to be executed.
Summarize
-
Once
It ensures that the passed function will only be executed once, which is often used in some initialized scenarios or singleton modes. -
Once
All rights reservedDo
The concurrent calls are safe, allThe operation after the call will definitely be corrected on the first time
f
Execute after the call. (Not obtainedf
The goroutine of execution rights will block) - even though
The inside
f
Appearedpanic
, will not be called again in the futuref
。
The above is the detailed content of the example analysis of the implementation principle of go sync Once. For more information on the implementation principle of go sync Once, please pay attention to my other related articles!