This is a creation in Article, where the information may have evolved or changed.
The sync package gives us a handy set of standard libraries for synchronizing semantics, so let's focus on sync. How mutex mutexes are implemented. My native source code installation path in/usr/local/go, this sync. The relevant code involved in the Mutex (Golang 1.3 version ) is mainly:
/usr/local/go/src/pkg/sync/mutex.go
/usr/local/go/src/pkg/sync/runtime.go
/usr/local/go/src/pkg/runtime/sema.goc
The first is mutex.go:
// A Mutex is a mutual exclusion lock. // Mutexes can be created as part of other structures; // the zero value for a Mutex is an unlocked mutex. type Mutex struct { state int32 sema uint32 }
First we see that the mutex is composed of state and Sema two, we can not be difficult to speculate that the internal implementation of the mutex depends on the semaphore used for Goroutine wake operation, the state is the lock preemption statistics, In fact, this approach is based on a method proposed by E.w.dijkstra in 1965 to accumulate wake-up counts with shaping variables. See paper: Semaphore (after I write a single article analysis).
const ( mutexLocked = 1 << iota // mutex is locked mutexWoken mutexWaiterShift = iota )
mutexlocked = 1 (binary is 1): Indicates that the mutex is in a locked state.
Mutexwoken = 2 (binary is 10): Indicates that the mutex is in a wake state.
Mutexwaitershift = 2 (binary 10): Represents a left shift that waits for a hold lock to accumulate count.
Next is the core lock:
// Lock locks m. // If the lock is already in use, the calling goroutine // blocks until the mutex is available. func (m *Mutex) Lock() { // Fast path: grab unlocked mutex. if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) { if raceenabled { raceAcquire(unsafe.Pointer(m)) } return }
All raceenabled related code is ignored, which is the diagnostic code used internally by Thread-sanitizer to scan for thread safety issues Golang.
First, the code uses the CPU's CAS directive, modifies the state value, and if the value is 0, then it is mutexlocked (i.e.: 1), and if successful, indicates that the lock contention succeeds and return directly.
awoke := false
Awoke indicates that the goroutine wake caused by the semaphore release, at the bottom of the for loop, we see that the Runtime_semacquire returned will be set to true.
for { old := m.state new := old | mutexLocked if old&mutexLocked != 0 { new = old + 1<<mutexWaiterShift }
Enter the For loop to begin contention, knowing success. For a contention failure, that is, State!=0,old stores the current state value, new stores the old and mutexlocked or operations, because the state may be released immediately, so the locked bit must be set first, and subsequent retry When lock is new, it is directly CAs to state.
Old&mutexlocked!=0 says that if a lock is not released, the new value requires a new preemption count (1<<2 is actually a +4 operation).
if awoke { // The goroutine has been woken from sleep, // so we need to reset the flag in either case. new &^= mutexWoken }
If it is a wake-up operation, we need to erase the mutexwoken bit bit, which is done using an XOR operation and operation (XOR the new and the Mutexwoken, and then the new operation).
if atomic.CompareAndSwapInt32(&m.state, old, new) { if old&mutexLocked == 0 { break } runtime_Semacquire(&m.sema) awoke = true } }
Then to the retry lock step, said before, because the lock holder may immediately release the lock, so do one step retry operation can use as little as possible the amount of semaphore to sleep and wakeup overhead, and the wake-up operation also requires a new round of CAS judgment.
If the current state and old are equal to indicate that no other contention has been modified (some words have been restarted), and old&mutexlocked = 0 means that the lock has actually been released, then the CAs in the previous step set the lock to locked, the function break, If the lock is successful, the semaphore is down.
Semaphore down operation, detects whether the SEMA value is greater than 0, if it is greater than 0, the atom minus one, goroutine into the ready state, continue to fight the lock; otherwise goroutine enters sleep to wait for wake-up status.
Then look at the corresponding unlock operation:
Unlock unlocks M. It is a run-time error if m are not locked on entry to Unlock. A locked Mutex is isn't associated with A Particular goroutine. It is allowed for one goroutine to lock a mutexes and then//arrange for another goroutine to unlock It. Func (M *mutex) Unlock () {if raceenabled { _ = M.state Racerelease (unsafe. Pointer (m))} Fast path:drop lock bit. NEW: = Atomic. AddInt32 (&m.state, -mutexlocked) if (new+mutexlocked) &mutexlocked = = 0 { Panic ("Sync:unlock of Unlocked Mutex")}
The first step is the atomic operation to erase the locked bit bit to new, in order to avoid the multiple unlock operation with the new reset locked and mutexlocked to check with the judgment, but it seems there is an ABA bug, because there may be a continuous n-th unlock, Although a part of the panic, but another person will succeed.
Old: = new for { If There is no waiters or a goroutine has already//been woke N or grabbed the lock, no need to wake anyone. If Old>>mutexwaitershift = = 0 | | old& (mutexlocked|mutexwoken)! = 0 {return }//Grab the right to wake someone. New = (Old-1<<mutexwaitershift) | Mutexwoken if atomic. CompareAndSwapInt32 (&m.state, old, new) {Runtime_semrelease (&m.sema) return} Old = M.state}
Old saves the new value and enters the for loop, in order to avoid unnecessary wake-up operations, which determines whether there are people waiting to wake up and whether there is contention and wake-up conditions.
Old>>mutexwaitershift==0: Indicates that old does not currently have any waiters.
old& (Mutexlocked|mutexworken)!=0: Indicates a contention exists and has triggered a wake-up, consider executing NEW: = Atomic. AddInt32 (&m.state,-mutexlocked), suddenly someone tries to lock, then the new = old + 4 in lock is assigned to state, at this time the contention has immediately entered the logic of retry lock, Will eventually lock up successfully (note that there is only one ready state of goroutine at this time)
new = (old - 1<<mutexWaiterShift) | mutexWoken
Represents the new value minus the count of one waiter, and then puts the woken in place.
There is also the case that the execution of the Atomic.compareandswapint32 (&m.state, old, new) operation failed, state is in contention, there is no need to repeat the wake up, because the contention may have been grabbed the lock (the author this piece of thought is quite perfect).
If the CAs succeeds, the runtime_semrelease will eventually be called to wake up the Goroutine, and the atomic operation fails only to start over.
The entire mutex lock and unlock analysis is complete, I will focus on the next Runtime_semacquire and runtime_semrelease in Golang Runtime is how to dispatch goroutine.