マルチプロセスとは違い、マルチスレッド環境ではメモリ空間を共有している。
そのため、共有リソースに対して適切な同期制御(排他制御?)を行わないと共有リソースに対する更新処理を行う際に、データの不整合が発生する可能性がある。
これらの問題の解決策として以下のような同期制御方法が存在する
セマフォ
異なるスレッド間で共有される変数などの共有資源に対する 同時アクセス数を制限する機構
大まかにいうと、あるリソースが何個使用可能かを示す数値
あるリソースを占有するさい、セマフォの値を1減算し、リソースの開放を行う際に、セマフォの値を1加算する
セマフォの値が正の数でなければそのリソースを占有することはできない
カウンティングセマフォ
任意個の資源を扱うセマフォ
- カウンタ値に1加算してから待機中スレッドを起こす[V操作, Up, Signal, Post]
- カウンタ値が0より大きくなるまで待機してからカウンタ値を1減算する[P操作, Down, Wait, Pend]
バイナリセマフォ
値が0と1に制限されている(ロック/アンロック、使用可能/使用不可の意味がある)セマフォ
バイナリセマフォによって、複数スレッドから共有資源アクセスの相互排他(Mutual Exclusion)制御が実現される
後述するMutexでも実現可能
ただし、Mutexは複数スレッド間の相互排除に特化しているので、Mutexを使うのが好ましい
Mutex
複数スレッドから共有資源へのアクセス相互排他(MUTual EXclusion)制御を実現する機構
あるタイミングにおいて共有資源へアクセス可能なスレッドがただ1つしか存在しないことを保証する
ミューテックスにより相互排他制御される対象は、共有資源としての変数やデータ構造である
各スレッド上にて実行されるプログラムのコード区間ではない
つまり、あるスレッドの中でリソースをロックしつつ、待機状態になった場合、そのスレッドが再び実行状態になりそのリソースに対してロックの解放を行わない限り、ロックされているリソースに対して他のスレッドがアクセスできない状態が続く
ロック
Mutexでは、ロックされている/ロックされていない の2値状態
- 他スレッドがロック所有権を解放するまで待機し、自スレッドがロック所有権を獲得する[Lock, Acquire]
- 自スレッドが所有中のロックを解放し、同ミューテックスに対して待機中の他スレッドへ通知する[Unlock, Release]
クリティカルセクション
あるスレッドがミューテックスのロックを所有した状態で実行するコード区間
クリティカルセクションはソースコード上に表現されているコード区間(レキシカル・スコープ)だけでなく、そのスレッドの実行パス上にある全てのコード区間(ダイナミック・スコープ)を指す
あるミューテックスのロックを所有したまま呼び出した関数のコード区間は、関数呼出元で開始したクリティカルセクションに包含される
適切な並列・並行処理設計の下では、クリティカルセクションの範囲を可能な限り狭くすべきであり、ロックを所有したまま何らかの条件が満たされるまでスレッドを待機するといった設計はダメ
この場合後述する条件変数を用いるのがよい
条件変数(Condition Value)
「共有資源としてのデータ構造が、特定の状態に変化するまで待機」したい状況がよくある。
例えば複数スレッド間でデータを安全に送受信する有限長のFIFO待ち行列データ構造を考えると、取出側は有効なデータが存在するまで待機、挿入側は空きができるまで待機する必要がある。
これを実現するのが条件変数であり、ミューテックスで保護されるデータ構造が、特定条件を満たすまで効率的に待機する機構 の実装を助ける
- 関連付けられたミューテックスのロックを解放し、条件変数に通知があるまで自スレッドを待機し、再びロック獲得する[Wait]
- 同じ条件変数に対して待機中のスレッド群のうち、ランダムな1スレッドに対して通知する[Notify, Signal]
- 同じ条件変数に対して待機中の全スレッドに対して通知する[NotifyAll, Broadcast]
セマフォとMutexの違い
セマフォにはそのリソースのロック所有権という概念がない。
一方Mutexにはロック所有権という概念が存在し、ロック解放操作は、ロックを所有しているスレッドでしか行えない