Home  >  Article  >  Backend Development  >  Detailed explanation of examples of internal implementation of Golang mutex lock

Detailed explanation of examples of internal implementation of Golang mutex lock

零下一度
零下一度Original
2017-07-02 11:07:001685browse

This article mainly introduces the detailed internal implementation of Golang mutex lock. The editor thinks it is quite good. Now I will share it with you and give it as a reference. Let’s follow the editor and take a look.

The go language provides an out-of-the-box way of sharing resources, mutex lock (sync.Mutex), a zero value of sync.Mutex means that one is not locked Yes, it can be used directly. After a goroutine obtains the mutex lock, other goroutines can only wait until the goroutine releases the mutex lock. There are only two functions exposed in the Mutex structure, namely Lock and Unlock. When using the mutex lock It is very simple and this article does not explain its use.

Never copy values ​​when using sync.Mutex, because this may cause the lock to expire. When we open our IDE and jump to our sync.Mutex code, we will find that it has the following structure:


type Mutex struct {
 state int32   //互斥锁上锁状态枚举值如下所示
 sema uint32  //信号量,向处于Gwaitting的G发送信号
}

const (
 mutexLocked = 1 << iota // 1 互斥锁是锁定的
 mutexWoken       // 2 唤醒锁
 mutexWaiterShift = iota // 2 统计阻塞在这个互斥锁上的goroutine数目需要移位的数值
)

The above state values ​​are 0 (available) 1 (locked) 2~31 WaitingQueueCount

The following is the source code of the mutex lock. There will be four more important methods that need to be explained in advance, namely runtime_canSpin, runtime_doSpin, runtime_SemacquireMutex, runtime_Semrelease,

1, runtime_canSpin: More conservative spin, the spin lock in golang will not spin all the time, the runtime_canSpin method in the runtime package has some restrictions, The passed iter is greater than or equal to 4 or the number of CPU cores is less than or equal to 1, the maximum logical processor is greater than 1, there is at least a local P queue, and the local P queue can run the 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 the proyield function, which is also implemented in assembly language. Inside the function Loop calls the PAUSE instruction. The PAUSE instruction does nothing, but it consumes CPU time. When executing the PAUSE instruction, the CPU will not perform unnecessary optimizations on it.


//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的Lock函数定义如下

func (m *Mutex) Lock() {
    //先使用CAS尝试获取锁
 if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
        //这里是-race不需要管它
 if race.Enabled {
  race.Acquire(unsafe.Pointer(m))
 }
        //成功获取返回
 return
 }

 awoke := false //循环标记
 iter := 0    //循环计数器
 for {
 old := m.state //获取当前锁状态
 new := old | mutexLocked //将当前状态最后一位指定1
 if old&mutexLocked != 0 { //如果所以被占用
  if runtime_canSpin(iter) { //检查是否可以进入自旋锁
  if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
   atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) { 
                    //awoke标记为true
   awoke = true
  }
                //进入自旋状态
  runtime_doSpin()
  iter++
  continue
  }
            //没有获取到锁,当前G进入Gwaitting状态
  new = old + 1<<mutexWaiterShift
 }
 if awoke {
  if new&mutexWoken == 0 {
  throw("sync: inconsistent mutex state")
  }
            //清除标记
  new &^= mutexWoken
 }
        //更新状态
 if atomic.CompareAndSwapInt32(&m.state, old, new) {
  if old&mutexLocked == 0 {
  break
  }
             
            // 锁请求失败,进入休眠状态,等待信号唤醒后重新开始循环
  runtime_SemacquireMutex(&m.sema)
  awoke = true
  iter = 0
 }
 }

 if race.Enabled {
 race.Acquire(unsafe.Pointer(m))
 }
}
Mutex的Unlock函数定义如下

func (m *Mutex) Unlock() {
 if race.Enabled {
 _ = m.state
 race.Release(unsafe.Pointer(m))
 }

 // 移除标记
 new := atomic.AddInt32(&m.state, -mutexLocked)
 if (new+mutexLocked)&mutexLocked == 0 {
 throw("sync: unlock of unlocked mutex")
 }

 old := new
 for {
 //当休眠队列内的等待计数为0或者自旋状态计数器为0,退出
 if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken) != 0 {
  return
 }
 // 减少等待次数,添加清除标记
 new = (old - 1<<mutexWaiterShift) | mutexWoken
 if atomic.CompareAndSwapInt32(&m.state, old, new) {
            // 释放锁,发送释放信号
  runtime_Semrelease(&m.sema)
  return
 }
 old = m.state
 }
}

The simplest case is that the mutex lock has no conflict. When there is a conflict, spin is performed first, because most of the code segments protected by Mutex It is very short and can be obtained after a short spin; if the spin wait fails, you have to use the semaphore to make the current Goroutine enter the Gwaitting state.

The above is the detailed content of Detailed explanation of examples of internal implementation of Golang mutex lock. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn