1. ホーム
  2. スクリプト・コラム
  3. ゴラン

go言語における並行処理のセキュリティとロックの説明

2022-02-14 21:57:42

この記事を読んで、ロックについての理解を深めることから始めるとよいでしょう。

GO並行プログラミングにおける相互排除ロックと読み取り/書き込みロック

Mutex - 相互排除ロック

Mutexは、主にCAS命令+スピン+セマフォで実装されています。

データ構造

var='LotArea'
data=pd.concat(df_train['SalePrice'],df_train[var],axis=1)
data.plot.scatter(x=var,y='SalePrice',ylim=(0,800000))

上記の2つの構造体は、合わせても8バイトのスペースしか取らず、Go言語では相互に排他的なロックを表します。

状態です。

デフォルトでは、相互排他的ロックのすべてのステータスビットは0である。 var='LotArea' data=pd.concat([df_train['SalePrice'],df_train[var]],axis=1) data.plot.scatter(x=var,y='SalePrice',ylim=(0,800000)) の異なるビットは

  • 1ビットはロックされているかどうかを示す
  • 1ビットは、同時進行中のプロセスが起動されたかどうかを示す
  • 1ビットで飢餓状態であるかどうかを示す
  • 残りの29ビットは、ブロッキングしているコプロセスの数を示す

通常モードと飢餓モード

通常モード:すべてのゴルーチンがFIFO順にロック獲得を行い、起動中のゴルーチンと新しく要求されたゴルーチンが同時にロック獲得を行い、通常新しく要求されたゴルーチンがロックを獲得しやすく(CPUを連続的に占有)、起動中のゴルーチンがロックを獲得しやすいわけではありません。

飢餓モード:ロックを取得しようとするすべてのゴルーチンが並んで待ち、ロックを要求する新しいゴルーチンはロック取得を行わず(スピンを無効にして)、ロック取得を待つキューの末尾に参加します。

ゴルーチンが相互排他的ロックを取得し、それがキューの最後であるか、1ms未満の待ち時間であれば、現在の相互排他的ロックは通常モードに切り替わります。

通常モードのミューテックスは、スターベーションモードよりも性能が良く、Goroutine がロックの取得待ちで立ち往生することによる高いテールレイテンシを回避することができます。

ミューテックスロックのロック処理

  • ロックが初期状態の場合、直接ロックされる
  • ミューテックスロックがロック状態にあり、通常モードで動作している場合、goroutineはスピンに入り、ロックが解放されるのを待ちます。

ゴルーチンがスピンに入るための条件は、次のように非常に厳しいものです。

  • インターロックは、通常モードでのみスピンに入ることができます。
  • runtime.sync_runtime_canSpin 真を返す必要がある

マルチCPUのマシンで動作している。

現在のGoroutineがそのロックを取得するためにスピンに入る回数が4回未満である。

現在のマシンに少なくとも1つの実行プロセッサPがあり、処理のための実行キューが空である。

  • 現在のGoroutineがロックのために1ms以上待つと、ミューテックスロックはスターベーションモードに切り替わります。
  • 通常の条件下ではミューテックスロックは通過する runtime.sync_runtime_SemacquireMutex ロックを取得しようとする Goroutine を休止状態に切り替え、ロック保持者が目を覚ますのを待ちます。
  • 現在の Goroutine が Mutex 上で最後に待機している同時接続であるか、1ms 未満であれば、 Mutex を通常モードに切り替えます。

ミューテックスロックを解除する処理

相互排他的ロックが既に解除されている場合、再度ロックを解除すると例外が発生する

ミューテックスロックがスターベーションモードになり、待ち行列の先頭のGoroutineにロックの所有権を与える場合

は、ロック解放を待っている Goroutine がない場合、あるいは、すでに起きている Goroutine がロックを獲得している場合は直接戻り、それ以外の場合は、対応する Goroutine を起こして 戻ります。

ミューテックスロックの使用は、2つ以上のGoroutinesでロックされ、ロック解除されることはありませんグローバルに同じミューテックスを使用していないビジネスを記述することをお勧めしますミューテックスは(関数のパラメータを介して渡されない含む)コピーされてはならない、それ以外の場合は参照を渡す前にロックの状態をコピーします。これは、デッドロックを生成するのは簡単ですが、キーは、コンパイラがこのデッドロックを見つけることができないことです。

RWMutex - リード/ライトロック

Go の RWMutex は書き込み優先の設計を使用しています。

データ構造

type RWMutex struct {
	w Mutex // multiplex the capabilities provided by the mutex lock
	writerSem uint32 //writer semaphore
	readerSem uint32 //reader semaphore
	readerCount int32 // stores the number of read operations currently being performed
	readerWait int32 // indicates the number of read operations waiting to complete when a write operation blocks
}

書き込みロック

書き込みロックを取得します。

  • ロックは、構造体が保持するMutex構造体のMutex.Lockを呼び出すことで、以降の書き込み操作をブロックします。
  • ロックする readerCount を2^30で負数にし、その後の読み込み操作をブロックする。
  • 他のGoroutineがリードロックを保持している場合、そのGoroutineはすべてのリードロックが実行され解放されるまでスリープ状態になる writerSem セマフォは現在のワーカーを目覚めさせる

書き込みロックを解除する。

  • を解放します。 readerCount を正の数に戻し、読み取りロックを解除します。
  • リードロックでスリープしているGoroutineをすべてウェイクアップさせる
  • Mutex.Unlockを呼び出して書き込みロックを解除する。

書き込みロックを取得すると、読み取りロックを取得する前に書き込みロックの取得をブロックします。これは、連続した書き込み操作によって読み取り操作が「飢餓」に陥らないようにするための戦略です。

読み取りロック

リードロックの取得

リードロックを取得するための方法 sync.RWMutex.RLock 簡単に言うと、このメソッドは readerCount に1を加えたものです。

  • このメソッドが負の数を返した場合(他のゴルーチンが書き込みロックを取得したことを表し、現在のゴルーチンはロックが解放されるのを待つためにスリープ状態になります)。
  • もしこのメソッドが負でない数字を返した場合、つまりどのゴルーチンも書き込みロックを取得していない場合、このメソッドは成功裏に

読み取りロックを解除する

リードロックの解除方法 sync.RWMutex.RUnlock このメソッドでは

  • 意志 readerCount をマイナスして、戻り値に応じて別途処理されます。
  • 戻り値が0以上の場合、読み取りロックは直接解除される
  • 0より小さい場合は、書き込み操作が行われていることを意味します。 sync.RWMutex.rUnlockSlow そうすれば readerWait を1つ減らし、すべての読み取り操作が解放されたときにセマフォをトリガーします。 writerSem このセマフォがトリガーされると、スケジューラは書き込みロックを取得しようとするGoroutineを起動します。

待機グループ

sync.WaitGroup Goroutineのグループのリターンを待つことができます。

sync.WaitGroup 3つのメソッドが外部に公開されている。

<テーブル メソッド名 機能 (wg * WaitGroup) Add(delta int) カウンタ+デルタ (wg *WaitGroup) Done() カウンタ-マイナス1 (wg *WaitGroup) Wait() カウンタが0になるまでブロック

sync.WaitGroup.Done のペアに過ぎない。 sync.WaitGroup.Add メソッドの単純なラッパーであり、add -1

Sync.Map

Go言語の組み込みマップは、コンカレンシーセーフではありません。

Go言語の sync パッケージは、マップのコンカレンシーセーフバージョンをすぐに利用できるようにします。 sync.Map . 相互排他的ロックを用いた並行性セキュリティ

データ構造

type Map struct {
    mu Mutex
    read atomic.Value // readOnly
    dirty map[interface{}]*entry
    misses int
}

Out-of-the-box とは、内蔵マップのように make 関数で初期化することなく、そのまま使えるという意味です。また sync.Map メソッドが組み込まれています。

<テーブル メソッド名 機能 (m *sync.Map)Store(key, value interface{}) キーと値のペアを保存する (m *sync.Map)Load(key interface{}) キーを元に対応する値を取得する (m *sync.Map)Delete(key interface{}) キーと値のペアを削除する (m *sync.Map)Range(f func(key, value interface{}) bool) sync.Mapを繰り返し処理します。Rangeの引数は関数 *sync.mapはLen()メソッドを持っていません。

アトミック操作(アトミックパッケージ)

コード中のロック操作は、カーネル状態でのコンテキスト・スイッチングを伴うため、時間とコストがかかることがあります。基本的なデータ型では、同時実行安全性のためにアトミック操作を使用することもできます。アトミック操作はユーザー ステートで実行できる方法で Go 言語が提供しているので、組み込み標準ライブラリ sync/atomic が提供しているロック操作よりも性能が良いからです。

参考にしてください。

Goの並行プログラミング、同期プリミティブとロック|Go言語の設計と実装(draveness.me)

今回の記事は、go言語における並行処理のセキュリティとロックについてです。go言語における並行処理のセキュリティとロックについての詳しい情報は、Script Houseの過去の記事を検索するか、以下の関連記事を引き続きご覧ください。