Sync for Golang 1.9. The mutex is analyzed in the same way as Golang 1.10, except that panic
is changed to throw
.
Source code Location: sync\mutex.go
. The
can see the following comments:
Mutex can in 2 modes of operations:normal and starvation. In normal mode waiters is queued in FIFO order, but a woken up waiter does not own the mutex and competes with new Arrivi Ng Goroutines over the ownership. New arriving Goroutines has an advantage--they is already running on CPU and there can is lots of them, so a woken up Waiter has good chances of losing. In such case it was queued at front of the wait queue. If a waiter fails to acquire the mutex for more than 1ms, it switches mutexes to the starvation mode. In starvation mode ownership of the mutex are directly handed off from the unlocking goroutine to the waiter at the front O f the queue. New arriving goroutines don ' t try to acquire the mutex even if it appears to be unlocked, and don ' t try to spin. Instead They queue themselves at the tail of the wait queue. If a waiter receives ownership of the mutex and sees that either (1) It's the last waiter in the queue, or (2) it waited For less than 1 ms, it switches mutex BACK to normal operation mode. Normal mode has considerably better performance as a goroutine can acquire a mutexes several times in a row even if there ar E blocked waiters. Starvation mode is important to prevent pathological cases of tail latency.
Bo Master English sucks, on a rough translation, for reference only:
互斥量可分为两种操作模式:正常和饥饿。在正常模式下,等待的goroutines按照FIFO(先进先出)顺序排队,但是goroutine被唤醒之后并不能立即得到mutex锁,它需要与新到达的goroutine争夺mutex锁。因为新到达的goroutine已经在CPU上运行了,所以被唤醒的goroutine很大概率是争夺mutex锁是失败的。出现这样的情况时候,被唤醒的goroutine需要排队在队列的前面。如果被唤醒的goroutine有超过1ms没有获取到mutex锁,那么它就会变为饥饿模式。在饥饿模式中,mutex锁直接从解锁的goroutine交给队列前面的goroutine。新达到的goroutine也不会去争夺mutex锁(即使没有锁,也不能去自旋),而是到等待队列尾部排队。在饥饿模式下,有一个goroutine获取到mutex锁了,如果它满足下条件中的任意一个,mutex将会切换回去正常模式:1. 是等待队列中的最后一个goroutine2. 它的等待时间不超过1ms。正常模式有更好的性能,因为goroutine可以连续多次获得mutex锁;饥饿模式对于预防队列尾部goroutine一致无法获取mutex锁的问题。
Read this explanation, then the basic business logic is understood, you can tidy up the clothes, ready to look at the code.
Open to mutex.go
see the following code:
type Mutex struct { state int32 // 将一个32位整数拆分为 当前阻塞的goroutine数(29位)|饥饿状态(1位)|唤醒状态(1位)|锁状态(1位) 的形式,来简化字段设计 sema uint32 // 信号量}const ( mutexLocked = 1 << iota // 1 0001 含义:用最后一位表示当前对象锁的状态,0-未锁住 1-已锁住 mutexWoken // 2 0010 含义:用倒数第二位表示当前对象是否被唤醒 0-唤醒 1-未唤醒 mutexStarving // 4 0100 含义:用倒数第三位表示当前对象是否为饥饿模式,0为正常模式,1为饥饿模式。 mutexWaiterShift = iota // 3,从倒数第四位往前的bit位表示在排队等待的goroutine数 starvationThresholdNs = 1e6 // 1ms)
You can see that the mutex contains:
- A non-negative signal volume SEMA;
- State indicates the status of the mutex.
Constant:
- Mutexlocked Indicates whether the lock is available (0 available, 1 occupied by another goroutine)
- Mutexwoken=2 Indicates whether the mutex is awakened
- Mutexwaitershift=4 indicates that the number of goroutine that are blocked on the mutex needs to be shifted.
Mapping 3 constants to a state is
state: |32|31|...| |3|2|1| \__________/ | | | | | | | | | | mutex的占用状态(1被占用,0可用) | | | | | mutex的当前goroutine是否被唤醒 | | | 饥饿位,0正常,1饥饿 | 等待唤醒以尝试锁定的goroutine的计数,0表示没有等待者
If the students are familiar with the Java lock, it will be found with Aqs design is similar, just no aqs design so exquisite, have to sigh, JAVA
the good.
Is there any question as to why you are using Int32 instead of Int64, because 32-bit atomic operations are better and of course meet the needs.
The mutex has two functions and in version 1.9 Lock()
Unlock()
.
Let's start by analyzing the most difficult Lock()
functions:
Func (M *mutex) lock () {//If m.state=0, indicates that the current object has not been locked, the atomic assignment operation is set to the mutexlocked state, COMPAREANSWAPINT32 returns TRUE//Otherwise the description object has been Other goroutine are locked, no atomic assignment operation is set, COPAREANDSWAPINT32 returns False if atomic. CompareAndSwapInt32 (&m.state, 0, mutexlocked) if race. Enabled {race. Acquire (unsafe. Pointer (M))} return}//start waiting timestamp var waitstarttime int64//Hunger pattern identification starving: = FALSE// Wake Up Logo awoke: = false//spin times ITER: = 0//Save current object lock state old: = m.state//See this for {} Description using CAs algorithm for { Equivalent to xxxx...x0xx & 0101 = 01, the current object lock is used if old& (mutexlocked|mutexstarving) = = mutexlocked && It makes sense to judge whether the current goroutine can enter the spin lock Runtime_canspin (ITER) {//active rotation. Try setting the Mutexwake flag to tell the unlock and not wake up the other blocked goroutines. If!awoke &&//Determine again if wake-up: xxxx...xx0x & 0010 = 0 Old&mutexwoken = = 0 && See if there are goroution in the queue Old>>mutexwaitershift! = 0&&//Change object lock to wake-up status: xxxx...xx0x | 0010 = xxxx...xx1x Atomic. CompareAndSwapInt32 (&m.state, old, old|mutexwoken) {awoke = True}//end_if_lock After entering the spin lock, the current goroutine does not hang, still occupy CPU resources, so after a certain number of retries, no longer enter the spin lock Logic runtime_dospin ()//self-added, indicating the spin times it er++//Save mutex object is about to be set to the state old = M.state continue}//End_if_spin//The following code is not Use * * Spin * * Case NEW: = old//Do not try to get hungry mutex, the new goroutines must be queued. Object lock hunger Bit changed, description in hunger Mode//xxxx...x0xx & 0100 = 0xxxx...x0xx if old&mutexstarving = = 0 {// xxxx...x0xx | 0001 = xxxx...x0x1, Identity object Lock is locked new |= mutexlocked}//xxxx...x1x1 & (0001 | 0100) = xxxx. . x1x1 & 0101! = 0; The current mutex is in hunger mode and the lock is occupied, and the newly added goroutine is placed behind the queue if old& (mutexlocked|mutexstarving)! = 0 { Update the number of blocked Goroutine, indicating the number of wait goroutine for the mutex plus 1 new + = 1 <<Mutexwaitershift}//The current Goroutine converts the mutex to hunger mode. However, if the mutex is not currently unlocked, do not turn on the switch and set the mutex status to hunger mode. Unlock expected to have hungry goroutine if starving &&//xxxx...xxx1 & 0001! = 0; Lock is already occupied old&m Utexlocked! = 0 {//xxxx...xxx | 0101 = xxxx...x1x1, Identity object lock locked new |= mutexstarving} Goroutine has been awakened, so it is necessary to reset the flag in both cases if awoke {//xxxx...xx1x & 0010 = 0 If the wake flag is not coordinated with awoke panic If New&mutexwoken = = 0 {Panic ("Sync:inconsistent Mutex State")}//N EW & (^mutexwoken) = xxxx...xxxx & (^0010) = xxxx...xxxx & 1101 = xxxx...xx0x: Set wake-up status bit 0, wake up New &^= Mutexwoken}//Gets the lock successfully if atomic. CompareAndSwapInt32 (&m.state, old, new) {//xxxx...x0x0 & 0101 = 0, Object lock already acquired if old& (mute xlocked|mutexstarving) = = 0 {//end CAS Break}//ToThe operation is to determine whether to return to normal mode from hunger Mode//determine in FIFO or LIFO mode Queuelifo: = waitstarttime! = 0 if Waitstar Ttime = = 0 {waitstarttime = runtime_nanotime ()} Runtime_semacquiremutex (&m.sema , queuelifo) starving = Starving | | Runtime_nanotime ()-waitstarttime > Starvationthresholdns old = m.state//xxxx...x1xx & 0100 ! = 0 if old&mutexstarving! = 0 {//xxxx...xx11 & 0011! = 0 if old& (m Utexlocked|mutexwoken)! = 0 | | Old>>mutexwaitershift = = 0 {Panic ("Sync:inconsistent Mutex State")} Delta: = Int32 (Mutexlocked-1<<mutexwaitershift) if!starving | | Old>>mutexwaitershift = = 1 {delta-= mutexstarving} atomic. AddInt32 (&m.state, delta) break} awoke = true iter = 0} else {//Save mutex object state old = m.state}}//CAs End if race. Enabled {race. Acquire (unsafe. Pointer (M))}}
See Lock ()
function is not feel a piece of state, tell everyone a method, see Lock ()
function need to think how to unlock. Let's start by looking at the Unlock ()
function.
Func (M *mutex) Unlock () {if race. Enabled {_ = m.state race. Release (unsafe. Pointer (M))}//State-1 identity unlock NEW: = Atomic. AddInt32 (&m.state,-mutexlocked)//Verify that the lock status complies with if (new+mutexlocked) &mutexlocked = = 0 {Panic ("Sync:un Lock of unlocked Mutex ")}//xxxx...x0xx & 0100 = 0; determine if in normal mode if new&mutexstarving = = 0 {old: = new for {//If no waiting goroutine or goroutine has been unlocked complete if Old>>mutexwaitershift = = 0 | | XXXX...X0XX & (0001 | 0010 | 0100) = XXXX...X0XX & 0111! = 0 old& (mutexlocked|mutexwo ken|mutexstarving)! = 0 {return}//Grab the right to wake someone. New = (Old-1<<mutexwaitershift) | Mutexwoken if atomic. CompareAndSwapInt32 (&m.state, old, new) {Runtime_semrelease (&m.sema, false) return } old = M.state}} ELSE {//Hunger Mode: Transfer ownership of the mutex to the next waiting goroutine//Note: Mutexlock is not set, Goroutine is set after wake-up. But the mutex is still considered locked, and if the mutex is set, the new goroutines will not get it runtime_semrelease (&m.sema, True)}}
There will be some go1.6-based analysis on the Internet, but the gap with go 1.9 is a bit big.
The above analysis, because the individual level is limited, inevitably has the mistake, please each teacher schoolmate many pointers, does not like the spray.
Appendix
Https://github.com/golang/go/blob/dev.boringcrypto.go1.9/src/sync/mutex.go
https://segmentfault.com/a/1190000000506960