This is a creation in Article, where the information may have evolved or changed.
Golang provides a synchronization mechanism with the mutex under the Sync module, Waitgroup, and the language itself, Chan, and so on. These synchronous methods are based on the underlying synchronization mechanism (CAS, Atomic, spinlock, SEM) implemented in runtime, this paper mainly discusses how to realize the synchronization mechanism of Golang.
1 CAs, atomic
CAS (Compare and Swap) and atomic operations are the basis of other synchronization mechanisms, implemented in RUNTIME/ASM_XXX.S (XXX stands for system architecture, such as AMD64). AMD64 architecture of the system, mainly through two assembly statements to achieve, one is LOCK, one is CMPXCHG.
LOCK is an instruction prefix followed by a "read-change-write" directive, such as Inc, XCHG, Cmpxchg, and so on. This instruction will be exclusive to the CPU cache access.
CMPXCHG is the instruction to complete the CAS action. With lock and CMPXCHG, the function of atomic cas is achieved.
The atomic operation is also implemented through the combination of LOCK and other arithmetic operations (XADD,ORB , etc.).
2 Spin Lock
The spin lock in Golang is used to implement other types of locks, and the spin lock acts like a mutex, except that it does not block the process by hibernation, but rather stays in a busy state (spin) until the lock is acquired, thus avoiding the process (or
The functions associated with the spin lock are Sync_runtime_canspin and Sync_runtime_dospin, which are used to determine whether a spin is currently possible and the latter performs spin operations. They are usually used together.
The Sync_runtime_canspin function returns false in the following four cases
- It's been executed many times.
- is a single-core CPU
- There are no other running P
- The G queue for the current P is empty
Condition 1 Avoid wasting CPU for a long time spin.
Conditions 2 and 3 are used to ensure that there are other goroutine running in addition to the currently running Goroutine.
Condition 4 is that the condition to avoid the spin lock wait is triggered by the other G of the current P , which causes the spin to become meaningless because the condition can never be triggered.
Sync_runtime_dospin calls the Procyield function, which is also an assembly language implementation. The function internally loops the call to the PAUSE instruction. The pause instruction does nothing but consumes CPU time, and the CPU does not unnecessarily optimize the pause command when it is executed.
3 Signal Volume
Follow the comments in Runtime/sema.go:
Think of them as a way to implement sleep and wakeup
The SEMA in Golang provides the ability to hibernate and wake Goroutine.
The Semacquire function first checks if the semaphore is 0: if it is greater than 0, let the semaphore be reduced by one, return, or, if it equals 0, call the Goparkunlock function, put the current goroutine in the SEMA waiting queue, and set him as the waiting state.
The Semrelease function first adds a semaphore and then checks to see if there is a waiting goroutine: If not, return directly, or, if there is one, call the Goready function to wake up a goroutine.
4 Sync/mutex
The mutex has two methods of lockandUnlock , and the main realization idea is embodied in the lock function.
When Lock executes, it is divided into three cases:
- no conflict The current state is set to locking state by CAS operation;
- There is a conflict starting spin, and waiting for the lock to release, if the other Goroutine release the lock during this time, directly obtain the lock; if not released, enter 3;
- has a conflict and has passed the spin phase by calling the Semacquire function to allow the current goroutine to enter the wait state.
No conflict is the simplest case, when there is conflict, the first spin is considered in terms of efficiency, since most of the mutex-protected code snippets are short and can be obtained after a short spin, and if the spin wait is fruitless, the current goroutine will have to wait through the semaphore.