Detail the internal implementation of the Golang mutex
- 2020-06-03 06:55:03
- OfStack
go language offers a way out of the box to a Shared resource, the mutex (sync. Mutex), sync. Mutex zero said one not locked, can use directly, 1 goroutine acquire a mutex other goroutine can only wait until after the gorutine released the mutex, open only two functions in the Mutex structure, respectively is Lock and Unlock, when using a mutex is very simple, use the purpose of this article is not.
Never make a copy of the value when using sync.Mutex as this may cause the lock to fail. When we open our IDE and jump into our ES18en.Mutex code we will see that it has the following structure:
type Mutex struct {
state int32 // The enumeration values of the lock state on the mutex are shown below
sema uint32 // Semaphore Gwaitting the G Send a signal
}
const (
mutexLocked = 1 << iota // 1 A mutex is locked
mutexWoken // 2 Wake up the lock
mutexWaiterShift = iota // 2 Statistics are blocked on this mutex goroutine The number of values to shift
)
The state values above are 0(available) 1(locked) 2~31 wait queue count
The following is the source code of the mutex, there will be four more important methods to explain in advance, they are runtime_canSpin, runtime_doSpin, runtime_SemacquireMutex, runtime_Semrelease,
1. runtime_canSpin: Conservative spin, spin lock in golang does not spin 1 straight down, runtime_canSpin method in runtime package makes some restrictions, the iter passed is at most 4 or the cpu core is at least 1, the maximum logical processor is greater than 1, there is at least a local P queue, and the local P queue can run G queue is empty.
//go:linkname sync_runtime_canSpin sync.runtime_canSpin
func sync_runtime_canSpin(i int) bool {
if i >= active_spin || ncpu <= 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 procyield function, which is also assembly language implementation. The PAUSE instruction is called in a loop within the function. The PAUSE directive does nothing, but consumes CPU time, and CPU does not optimize it unnecessarily when it executes the PAUSE directive.
//go:linkname sync_runtime_doSpin sync.runtime_doSpin
func sync_runtime_doSpin() {
procyield(active_spin_cnt)
}
3, runtime_SemacquireMutex:
//go:linkname sync_runtime_SemacquireMutex sync.runtime_SemacquireMutex
func sync_runtime_SemacquireMutex(addr *uint32) {
semacquire(addr, semaBlockProfile|semaMutexProfile)
}
4, runtime_Semrelease:
//go:linkname sync_runtime_Semrelease sync.runtime_Semrelease
func sync_runtime_Semrelease(addr *uint32) {
semrelease(addr)
}
Mutex the Lock The function definition is as follows
func (m *Mutex) Lock() {
// Use the first CAS Attempt to acquire a lock
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
// Here is the -race Don't worry about it
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
// Successful retrieval return
return
}
awoke := false // Circulating markers
iter := 0 // Cycle counter
for {
old := m.state // Gets the current lock state
new := old | mutexLocked // Put the current state last 1 A specified 1
if old&mutexLocked != 0 { // If so is occupied
if runtime_canSpin(iter) { // Check to see if the spin lock is accessible
if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
//awoke Marked as true
awoke = true
}
// I'm going to spin
runtime_doSpin()
iter++
continue
}
// No lock was obtained, currently G Enter the Gwaitting state
new = old + 1<<mutexWaiterShift
}
if awoke {
if new&mutexWoken == 0 {
throw("sync: inconsistent mutex state")
}
// Remove tag
new &^= mutexWoken
}
// Update the status
if atomic.CompareAndSwapInt32(&m.state, old, new) {
if old&mutexLocked == 0 {
break
}
// Lock request failed , Go into hibernation , Wait for the signal to wake up and start the cycle again
runtime_SemacquireMutex(&m.sema)
awoke = true
iter = 0
}
}
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
}
Mutex the Unlock The function definition is as follows
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 state counter is 0 To quit
if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken) != 0 {
return
}
// Reduce wait times and add a clear flag
new = (old - 1<<mutexWaiterShift) | mutexWoken
if atomic.CompareAndSwapInt32(&m.state, old, new) {
// Release the lock , Send release signal
runtime_Semrelease(&m.sema)
return
}
old = m.state
}
}
The mutex non-conflict is the simplest case, when there is a conflict, spin first, because most Mutex protected code segments are very short, after a short spin can be obtained; If the spin wait fails, the semaphore is used to get the current Goroutine into Gwaitting.