マルチスレッドプログラミングに於いて、排他制御は永遠の課題。特にマルチコアになり難易度が増しました。
  • 排他制御とは?
Wikipediaで排他制御を調べてみると、コンピュータ・プログラムの実行において、複数のプロセスが利用出来る共有資源に対し、複数のプロセスからの同時アクセスにより競合が発生する場合に、あるプロセスに資源を独占的に利用させている間は、他のプロセスが利用できないようにする事で整合性を保つ処理の事をいうとあります。

これまでの経験上、排他制御のソフトウェア実装とは(シングルコアにおける)割込禁止排他オブジェクト(セマフォ、ミューテックス)ビジーループを使うスピンロックあたりと思ってますが、マルチコア時代の排他は何を使えばいいのか。深堀してみます。
  • マルチコア時代の排他について調べた
①何はともあれスピン・ロック
Linuxのスピン・ロックでは,1つの資源に対して1つのロック変数をメモリー上に用意します。そして,ロック変数を取得できたCPUだけが資源にアクセスできます。ロック変数を取得できなかったCPUは,ロック変数が取得できるまでループして待ち続けます。この状態を「ビジー・ウエイト」と呼びます。
スピン・ロックは,マルチプロセッサ・システムで手軽に排他制御を実現する方法としてよく利用されています。ただし,ビジー・ウエイト中のCPUは処理が実行できずに待たされた状態になりますから,ビジー・ウエイトが頻繁に発生すると処理効率が悪くなります。処理効率を考えるなら,スピン・ロックで排他制御する資源をなるべく細かく分けるなどの工夫が必要です。
ロック取れなかったCPUはビジーループして待つ。当たり前だけどビジーループ中のCPUは他の処理ができないため、効率は悪い。

とあるプロジェクトでマルチコアCPUで動くアプリを見てますが、多分ミューテックスとセマフォしか使ってない気がするとはいえ明示的にアフィニティ設定してないので、恐らくCPU0しか使われず、シングルコアな動作になってる気もする。

スピンロック。デバドラを解析してると管理構造体の変数の排他で使われてるのを見かけることが多々ある。Ethernetのマルチポートのデバドラを例に挙げるが、IRQ毎にアフィニティ設定を分ければ並列動作できるだろうが、スピンロックしてないと危ない。

②そして、割り込み禁止。
シングルコアのときは割り込み禁止すれば、コンテキスト割込が発生し、コンテキストスイッチできないので、スレッドが遷移しない。従って自然と排他される。しかしマルチコアのときは他のCPUが割込を発生させるため、排他が保証されない。

③セマフォのこと。

セマフォの取得を読むと、こんなことが書いてある。セマフォの中ではスピンロック使ってるみたい。だとすればマルチコアでも安全なのかな。
セマフォーは排他制御として使いますが、他にスピンロックというのがあります。スピンロックはロックを獲得(変数のチェック)するまで、ビジーウエイトと言ってループで待ちますが、セマフォーを他のタスクに実行を譲ります。「そしたらセマフォーの方がいいじゃん。」ということですが、「排他区間が短い場合、タスクスイッチングするより、ビジーウエイトでループし続けた方がパフォーマンスいいですね。」という事のようです。実は、このセマフォ、実装する上でスピンロックを使っているんですね。

スピンロックというのは、マルチCPUシステムでの排他制御を実現する手法として誕生したもののようで、単一CPUシステムでは割り込みを禁止すれば、それ以降の排他性は保障されますしかしマルチCPUでは他のCPUからの割り込みが発生するわけです。割り込み禁止のCLIはそのCPUに対してだけです。このような背景でスピンロックが登場したようです
④ミューテックスのこと。
スピンロックの使用を読むと、ミューテックスよりスピンロックの方が効率が良いことがある、なんてことが書いてある。大抵のプログラムはミューテックスを使いまくってる。スピンロックとmutexの速度比較を読むと速度差は2倍。スピンロックの方が効率が良いなら、そうすべきな気がするが、どうだろう。で、結局マルチコアでミューテックスを使うのは安全なのかが、よくわからない。
スピンロックは、主に共有メモリー型のマルチプロセッサ上での使用に適した低レベルの同期機構です。呼び出しスレッドが、すでに別のスレッドによって保持されているスピンロックを要求する場合、2 番目のスレッドは、そのロックが使用可能になったかどうかをテストするためのループに入ります。スピンはプロセッササイクルを浪費するため、ロックを獲得したら、短時間だけ保持するようにすべきです。呼び出し元は、ほかのスレッドがロックを獲得できるようにするためのスリープ操作を呼び出す前に、スピンロックを解除するようにしてください。

スピンロックは mutex と条件変数を使用して実装することもできますが、スピンロックを実行するための標準化された方法は pthread_spin_* 関数です。短期間のロックであれば、pthread_spin_* 関数に必要なオーバーヘッドははるかに少なくなります。

どういうロックを実行する場合も、スレッドのブロックを設定している間に消費されるプロセッサリソースと、ブロックされている間にスレッドによって消費されるプロセッサリソースとの間にトレードオフが発生します。スピンロックでは、スレッドのブロックを設定したあと、単純なループを実行して、ロックが使用可能になるまで不可分なロック処理を繰り返すためのリソースがほとんど必要ありません。スレッドは、待機している間もプロセッサリソースを消費し続けます。

スピンロックに比べると、mutex はスレッドのブロックにより大量のプロセッサリソースを消費します。相互排他ロックが使用できない場合、スレッドはスケジューリングの状態を変更して、自身を待機スレッドの待ち行列に追加します。ロックが使用可能になると、スレッドがロックを獲得する前に、これらの手順を逆にたどる必要があります。スレッドは、ブロックされている間、プロセッサリソースを消費しません。

したがって、スピンロックと mutex は別の目的に使用すると有効な場合があります。非常に短期間のブロックでは、スピンロックの方が全体的なオーバーヘッドは少なくなることがあります。スレッドがより長期間ブロックされる場合は、mutex の方が全体的なオーバーヘッドは少なくなることがあります。
④キャッシュのこと。

マルチスレッド – マルチコアプロセッサの重要なセクションを読むと、マルチコア環境において共有メモリ変数に排他をかける場合、キャッシュ全体の一貫性(キャッシュコヒーレンシ)を考慮しなければならないとのこと。さらにCPU とキャッシュのはなしを読んだところやはりマルチコア環境で共有変数を更新したら必ずキャッシュをFlashせよとのこと。あとは命令実行の並列化(アウト・オブ・オーダ実行)がされることを考慮しメモリバリアも行う、ってとこですかね。
すべてのスレッドが1つのシングルCPUから実行されるシングルコアプロセッサでは、メモリ内のミューテックス(またはセマフォなど)にアトミックなテストセット操作を使用してクリティカルセクションを実装するという考えは十分に簡単に思えます。あなたのプロセッサはあなたのプログラムのある場所からテストとセットを実行しているので、それは他のスレッドのように偽装されたあなたのプログラムの中の別の場所から必ずしもそれをすることはできません。

しかし、実際に複数の物理プロセッサを使用しているとどうなりますか?単純な命令レベルのアトミック性では十分ではないようです。2つのプロセッサが同時にテストと設定操作を実行するb / cでは、アトミック性を維持するために本当に必要なのは、共有メモリの場所へのアクセスです。ミューテックス。 (そして、共有メモリの場所がキャッシュにロードされている場合は、キャッシュ全体の一貫性についても対処する必要があります。
  • ひとりごと
マルチコア環境の排他制御は奥が深い。結局、セマフォとミューテックスをマルチコアで使うことの是非について完全に安心できる情報は得られなかった。ただ、直感的に心配してたことは現状起きてないので杞憂なのかもしれない、とも思う。でも確信をもってOKなんとなくOKは雲泥の差と思う。だから私は納得いくまで追及し続けようと思う。

あと組込みシステムにおける割り込み制御に関する研究を見つけた。体系的に書いてそうなので、近々読んでみたい。新しい発見があったら追記します。

続く
IMG_20190616_100043

スポンサードリンク