この記事では主に Golang のミューテックス ロックの詳細な内部実装を紹介します。編集者がそれを参考として共有します。エディターに従って、見てみましょう。go 言語は、リソースを共有するためのすぐに使用できる方法、mutex ロック (sync.Mutex) を提供します。sync.Mutex の値が 0 の場合は、ロックされていないことを意味します。ゴルーチンがミューテックス ロックを取得した後、他のゴルーチンは、ゴルーチンがミューテックス ロックを解放するまで待つことができます。ミューテックス構造で公開されている関数は、Lock と Unlock の 2 つだけです。ミューテックス ロックの使用は非常に簡単です。この記事ではその使用法については説明しません。
sync.Mutex を使用する場合は、値をコピーしないでください。ロックが期限切れになる可能性があります。 IDE を開いて sync.Mutex コードにジャンプすると、次の構造になっていることがわかります:
type Mutex struct { state int32 //互斥锁上锁状态枚举值如下所示 sema uint32 //信号量,向处于Gwaitting的G发送信号 } const ( mutexLocked = 1 << iota // 1 互斥锁是锁定的 mutexWoken // 2 唤醒锁 mutexWaiterShift = iota // 2 统计阻塞在这个互斥锁上的goroutine数目需要移位的数值 )
カウント 以下は、事前に説明する必要があるさらに 4 つの重要なメソッド、すなわち runtime_canSpin、runtime_doSpin、runtime_SemacquireMutex、runtime_Semrelease、
1 のソース コードです。 , golang のスピン ロックは、ランタイム パッケージの runtime_canSpin メソッドにいくつかの制限があります。渡される iter は 4 以上であるか、CPU コアの数は 1 以下です。最大論理プロセッサー。が 1 より大きく、ローカル P キューが少なくとも 1 つあり、ローカル P キュー実行可能な G キューが空です。//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:
loop は PAUSE 命令を呼び出します。 PAUSE 命令は何も行いませんが、CPU 時間を消費します。PAUSE 命令を実行すると、CPU は不必要な最適化を実行しません。
//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 } }
ミューテックスロック競合が発生しない場合は、競合があります。最初にスピンを実行し、なぜなら、ほとんどの Mutex で保護されたコード セグメントは非常に短く、短いスピンの後に取得できるためです。スピン待機が失敗した場合、現在のゴルーチンはセマフォを介して Gwaiting 状態に入らなければなりません。
以上がGolangのミューテックスロックの内部実装例を詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。