This article mainly introduces the details of Golang mutual exclusion lock internal implementation, small series feel very good, and now share to everyone, but also for everyone to do a reference. Let's take a look at it with a little knitting.
The go language provides a way to share resources out-of-the-box, with a mutex (sync). Mutex), sync. The 0 value of the mutex represents a non-locked, can be used directly, a goroutine obtains the mutex after the other goroutine can only wait until the Gorutine release the mutex, in the mutex structure only exposed two functions, respectively, lock and unlock, The use of mutex is very simple, this article does not elaborate.
You are using sync. Do not make a value copy of the mutex as this may cause the lock to fail. When we open our IDE and jump into our Sync.mutex code, we find that it has the following structure:
The type mutex struct { state int32//Mutex lock status enumeration value is shown below SEMA UInt32 //semaphore, sending signal to G in gwaitting}const (mutexlocked = 1 < < iota//1 Mutex is locked Mutexwoken //2 wake-up lock Mutexwaitershift = iota//2 statistics blocking the number of goroutine needed to be shifted in this mutex lock)
The state value above is 0 (available) 1 (locked) 2~31 waiting queue count
The following is the source of the mutex, there will be four more important methods need to be explained in advance, respectively, is runtime_canspin,runtime_dospin,runtime_semacquiremutex,runtime_semrelease,
1, Runtime_canspin: more conservative spin, Golang in the spin lock will not always spin down, in the runtime package Runtime_canspin methods to do some restrictions, To pass over the ITER large equals 4 or the CPU core number is equal to 1, the maximum logical processor is greater than 1, there is at least one local p queue, and the local p queue can run the G queue is empty.
Go:linkname sync_runtime_canspin sync.runtime_canspinfunc sync_runtime_canspin (i int) bool {if I >= Active_spin | | n CPU <= 1 | | Gomaxprocs <= int32 (sched.npidle+sched.nmspinning) +1 {return false} if P: = GETG (). M.p.ptr ();!runqempty (p) {return False} return True}
2, Runtime_dospin: will call the Procyield function, the function is also assembly language implementation. The function internally loops the call to the pause instruction. The pause instruction does nothing but consumes CPU time, and when the pause instruction is executed, the CPU does not make unnecessary optimizations to it.
Go:linkname sync_runtime_dospin Sync.runtime_dospinfunc Sync_runtime_dospin () {Procyield (active_spin_cnt)}
3, Runtime_semacquiremutex:
Go:linkname Sync_runtime_semacquiremutex sync.runtime_semacquiremutexfunc Sync_runtime_semacquiremutex (addr * UInt32) {semacquire (addr, Semablockprofile|semamutexprofile)}
4, Runtime_semrelease:
Go:linkname sync_runtime_semrelease sync.runtime_semreleasefunc sync_runtime_semrelease (addr *uint32) {Semrelease (addr)} The lock function of the Mutex is defined as the following func (M *mutex) lock () {//using CAs to try to acquire the lock if atomic. CompareAndSwapInt32 (&m.state, 0, mutexlocked) {//Here is-race does not need to tube it if race. Enabled {race. Acquire (unsafe. Pointer (M)}}//successful FETCH return} awoke: = FALSE//cyclic flag iter: = 0//loop counter for {old: = m.state//Get current lock status NEW: = OL D | mutexlocked//will be the current state the last one specified 1 if old&mutexlocked! = 0 {//If so occupied if Runtime_canspin (ITER) {//check whether the spin lock can be entered if!awoke &A mp;& Old&mutexwoken = = 0 && Old>>mutexwaitershift! = 0 && Atomic. CompareAndSwapInt32 (&m.state, old, Old|mutexwoken) {//awoke marked as true awoke = true} Enter spin state runtime_dospin () iter++ continue}//not acquired to lock, current G enters gwaitting state new = old + 1<<mutexwaiters Hift} If awoke {if New&mutexwoken = = 0 {throw ("Sync:inconsistent Mutex State")}//ClearExcept mark new &^= Mutexwoken}//Update status if Atomic. CompareAndSwapInt32 (&m.state, old, new) {if old&mutexlocked = = 0 {break}//Lock request failed, enter Hibernate, wait for the signal to wake up and restart the Loop Runtime_semacquiremutex (&m.sema) awoke = True iter = 0} if race. Enabled {race. Acquire (unsafe. Pointer (M)) The Unlock function of}}mutex is defined as func (M *mutex) Unlock () {if race. Enabled {_ = m.state race. Release (unsafe. Pointer (M))}//Remove tag NEW: = Atomic. AddInt32 (&m.state,-mutexlocked) if (new+mutexlocked) &mutexlocked = = 0 {throw ("Sync:unlock of Unlocked Mutex")} Old: = new for {//when the wait count in the sleep queue is 0 or the spin status counter is 0, exit if Old>>mutexwaitershift = = 0 | | old& (mutexlocked|mutexwoken)! = 0 {return}//decrease number of waits, add purge mark new = (Old-1<<mutexwaitershift) | Mutexwoken if atomic. CompareAndSwapInt32 (&m.state, old, new) {//release lock, send release signal runtime_semrelease (&m.sema) return} old = M. State}}
Mutex no conflict is the simplest case, when there is a conflict, spin first, because most of the mutex protection of the code snippet is very short, after a short spin can be obtained, if the spin wait for no results, it is necessary to use the semaphore to let the current goroutine into the gwaitting state.