OS プログラミング

【プログラミング】マルチコア環境における排他制御の課題

マルチスレッドプログラミングにおいて排他制御は永遠の課題

特にマルチコアになり難易度が増しました。

ちょっとマニアックな話題ですし、課題の根本的な解決までは至っていない単なるメモ書きです。

※字ばっかりですいません。

この記事で解決できること

  • マルチコア環境における排他制御の課題について知ることが出来る。

排他制御とは?

排他制御(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は雲泥の差と思う。だから私は納得いくまで追及し続けようと思う。

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

  • この記事を書いた人
  • 最新記事

ペイヴメント

ペイヴメントのエンジニア塾(当ブログ)では20年以上の経験から得られたプログラミング系ノウハウについてベテランにも満足して頂けるような内容の濃いコンテンツを初心者にも分かりやすい形で日々発信しています。【経歴】ベンチャーのソフトハウスで4年勤務後、精密機器メーカーのソフト開発部門に勤務し今に至ります。

-OS, プログラミング

Copyright© ペイヴメントのエンジニア塾 , 2020 All Rights Reserved.