ホームページ  >  記事  >  バックエンド開発  >  Golangのミューテックスロックの内部実装例を詳しく解説

Golangのミューテックスロックの内部実装例を詳しく解説

零下一度
零下一度オリジナル
2017-07-02 11:07:001682ブラウズ

この記事では主に 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数目需要移位的数值
)

上記の状態値は、0 (利用可能) 1 (ロック) 2~31 待機中です

キュー

カウント 以下は、事前に説明する必要があるさらに 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:

は、同じくアセンブリ言語で実装されている proyield 関数を呼び出します。関数内の

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 サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。