This is a creation in Article, where the information may have evolved or changed.
Overview
Sync/mutex is one of the underlying basic objects of the go language, used to build synchronization logic between multiple goroutine, and is therefore used by a large number of high-level objects.
Its working model is similar to the Linux kernel Futex object, the implementation is very concise, performance is guaranteed.
Data
type Mutex struct { state int32 sema uint32 }
The mutex object has only two numeric fields, divided into state (stored states) and SEMA (the amount of semaphores used to calculate the number of sleep goroutine).
The 0 value that is filled in during initialization sets the mutex to an unlocked state, while guaranteeing minimal time overhead.
This feature allows the mutex to be used as a sub-object of other objects.
State field
The state is defined as the Int32 type, which allows the atomic method (Sync/atomic) to be called to atomically set the status.
Each state field is divided into three status segments, meaning the following:
31 3 2 1 0 +----~~~----+-+-+-+ | S | |W|L| +----~~~----+-+-+-+ | | | | | | | \--- 锁定状态,0表示未锁定,1表示已锁定 | | | | | \----- 唤醒事件,0表示无事件,1表示mutex已被解除锁定,可以唤醒等待其它goroutine | | | \------- 保留位,保持为0 | \------------------- 等待唤醒以尝试锁定的goroutine的计数,0表示没有等待者
Method
Lock ()/Locked
func (M *mutex) lock () {//fast path: Lock Mutex directly//record as FG Sync point: Fast Grab if atomic. CompareAndSwapInt32 (&m.state, 0, mutexlocked) {//Only execute if raceenabled without a waiting person, no wake-up event, no lock Raceacquire (unsafe. Pointer (M))} return} awoke: = False for {old: = m.state NEW: = Old | mutexlocked If old&mutexlocked! = 0 {//in locked state, increase the waiting count new = old + 1<<mutexwaitershift} If awoke {//Goroutine has been awakened, the "consume" wake event, reset flag bit new &^= Mutexwoken}//is recorded as G Sync point: Grab if atomic. CompareAndSwapInt32 (&m.state, old, new) {//new and existing state consistent, not modified by other goroutine if old&mutexlocked = = 0 {//Success Lock Break}//Hibernation wait Runtime_semacquire (&m.sema) awoke = true}//The old and new state is inconsistent, re-fetch the state and try to lock} if raceenabled {raceacquire (unsafe. Pointer (M))}}
Unlock ()/Unlocked
func (m *Mutex) Unlock() { if raceenabled { _ = m.state raceRelease(unsafe.Pointer(m)) } // 快速路径:直接解除锁定 // 记为FD同步点:Fast Drop new := atomic.AddInt32(&m.state, -mutexLocked) if (new+mutexLocked)&mutexLocked == 0 { // 连续解除两次以上 panic("sync: unlock of unlocked mutex") } old := new for { // 如果没有等待者,或已经有等待者被唤醒,或已经有goroutine锁定mutex, // 则无需尝试唤醒 if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken) != 0 { return } // 产生唤醒事件,尝试唤醒某个等待者 new = (old - 1<<mutexWaiterShift) | mutexWoken // 记为W同步点:Wake Up if atomic.CompareAndSwapInt32(&m.state, old, new) { // 新旧状态一致,没有被其它goroutine修改 // 尝试唤醒 runtime_Semrelease(&m.sema) return } // 新旧状态不一致,重新取状态并尝试唤醒 old = m.state }}
It can be observed that there are four atomic function call points in two functions, that is, the state synchronization point.
Once the atomic function call fails, the state is changed by another goroutine, and the state needs to be retrieved to determine the subsequent action.
Called by
Mutexes are not linked to specific goroutine and can be called by arbitrary goroutine, but there are several points to note:
- The same goroutine cannot invoke the lock () function two times in "continuous", otherwise it will cause a deadlock;
- In order to ensure that the call sequence is correct, you can call lock () in one goroutine and call unlock () in the other.
- The same mutex cannot be "contiguous" calling the Unlock () function two times, or it will result in an exception.
State Migration
Status list
The state's three state segments have a combination of the following States ( -
representing 0):
State Combination |
Dormant Person |
Wake-Up events |
Lock |
--- |
- |
- |
- |
--L |
- |
- |
Is |
-WL |
- |
Yes |
Is |
S-L |
Yes |
- |
Is |
SW- |
Yes |
Yes |
- |
S-- |
Yes |
- |
- |
-W- |
- |
Yes |
- |
SWL |
Yes |
Yes |
Is |
Combined with synchronization point analysis, the following rules can be obtained:
1. The non-wake Goroutine does not consume the wake-up event when attempting to lock, and does not change the state segment;
2. Wake-up Goroutine must consume wake-up events and the state segment will be reset;
3. Attempt to send wake-up events after unlocking.
Migration path
There is only one goroutine call case
In this case, the state is ---
--L
migrated only between (Paths 1 and 2), and only the FG and D two synchronization points are involved.
Case with two Goroutine calls
In this case, the state involves,,,,, ---
--L
S-L
S--
-W-
and -WL
six states.
Suppose that two goroutine are called X and Y, respectively:
1. 当X已经锁定mutex,而Y尝试锁定时,会从`--L`迁移到`S-L`(路径3); 2. 当X解除锁定后,会从`S-L`迁移到`S--`(路径5),此时Y还在休眠中; 3. 当X发送唤醒事件后,会从`S--`状态迁移到`-W-`(路径7),并尝试唤醒休眠者(即Y),此时有三条路径: 3.1 Y成功锁定mutex,则从`-W-`迁移到`--L`(路径8),X若尝试锁定,则进一步迁移到`S-L`(路径3); 3.2 X又尝试锁定mutex,则从`-W-`迁移到`-WL`(路径14),Y因被唤醒而尝试锁定,则进一步迁移到`S-L`(路径17); 3.3 X又尝试锁定mutex,且在Y尝试锁定前解除锁定,则从`-W-`迁移到`-WL`(路径14)而后又迁移回`-W-`(路径15)。
Case with multiple Goroutine calls
This condition is a continuation of the two goroutine invocation condition, which is again involved -WL
and two states on the original basis SWL
.
Suppose that three goroutine are called X, Y, and Z, respectively:
1. 当X已经锁定mutex,则Y在等待,且Z尝试锁定,会从`S-L`迁移到其自身(路径4); 2. 当X解除锁定、还未唤醒Y时,此时若Z尝试锁定,会从`S--`迁移回`S-L`(路径6); 3. 当X解除锁定、且唤醒Y后(路径9),此时若Z尝试锁定,会从`SW-`迁移到`SWL`(路径11),此时有三条路径: 3.1 Y发现mutex已被锁定,而进一步迁移到`S-L`状态(路径18); 3.2 Z迅速解除锁定,状态迁移回`SW-`(路径12),Y尝试锁定,进一步迁移到`S-L`(路径10); 3.3 更多的goroutine尝试锁定,会一直迁移回`SWL`(路径13)。4. 当X唤醒Y、且Z尝试锁定成功,则从`-WL`迁移到`SWL`(路径16)。
Appendix
Graphviz source of the attached state migration diagram.
Graph {node [shape= "Circle"]; Edge [dir= "forward", len= "2.0"]; RANKDIR=LR; nnn [label= "---"]; NNL [label= "--l"]; SWN [label= "sw-"]; SNN [label= "s--", pos= "0,0"]; SNL [label= "S-l"]; NWN [label= "-w-"]; NWL [label= "-WL"]; SWL [label= "SWL"]; NNN-NNL [label= "1"]; NNL-nnn [label= "2"]; NNL-SNL [label= "3"]; SNL-SNL [label= "4"]; SNL-SNN [label= "5"]; SNN-SNL [label= "6"]; SNN-Nwn [label= "7"]; NWN-NNL [label= "8"]; SNN-Swn [label= "9"]; SWN-SNL [label= "10"]; SWN-SWL [label= "11"]; SWL-Swn [label= "12"]; SWL-SWL [label= "13"]; NWN-NWL [label= "14"]; NWL-Nwn [label= "15"]; NWL-SWL [label= "16"]; NWL-SNL [label= "17"]; SWL--SNL [label= "];}