【ソフトウェア開発】排他制御について解説

今回は排他制御について書かせて頂きます。
IT業界で働き始めている人の中には排他制御を経験している人もいるでしょう。

実生活の自動車パーキングの例を利用してご説明いたしましょう。
駅前やデパートの周辺で良く見かけることが多い自動車パーキングは最近は無人で入出場を管理しているケースが少なくないです(管理人らしき常駐のおじさんはお見かけしますが)
またその管理の方法として上下に動作する電車の踏切のようなゲートが利用されていることが多いです。

このゲートは何のためにあるか、お気付きになったことも多いでしょう。複数の車が我先にと同時に入場しようとする(「割り込む」)ことを防ぐためのしくみです。

実生活での排他制御ともいうことができると思います。整理してみます。
入出場を制限するゲート(排他制御を実現するためのロック機構)が複数の車同士(スレッド同士)が同時に駐車場内(共有メモリ)へ入場しようとする(割り込もうとする)ことを禁止する。
ITの世界の排他制御とまさに同じ概念です。

ITの世界で駐車場への入出場に相当するものとしては、下記のようなものが上げられます

  • データベースの更新・参照
  • 共有メモリへの設定・参照
  • フラッシュメモリへの書き込み・読み出し
  • 入出力レジスタへの書き込み・読み出し
  • 外部ファイルへの書き込み・読み出し 等々

ITの世界では複数のスレッド間で上記の処理に対する排他制御が必要になります。
ITの世界では実社会同様、排他制御の実現方法は色々あります。

中でも代表的なものを2、3、下記に記載致します。

排他制御実現方法

C言語の場合

  • セマフォ(semaphore)
    ==> 組み込みソフトでよく見ますが、あまり多様しすぎますとデッドロック(スレッド間で同一の資源の待合いが起こり、動作が固まってしまう状況)が発生する障害例もよく見聞きします。
  • ミューテックス(mutex)
    ==> あまり見たことがありません。しかしこの言葉は有名ですね。

JAVA言語の場合

synchronized修飾子
==> Javaの排他制御を行う場合、真っ先に思い浮かぶ定番です。マルチスレッドでデータベースやファイルへの入出力を行う場合は必須です。

しかし、注意して頂きたいことがあります。
上記で紹介したような排他制御はスレッド間では有効ですが、いわゆる割り込み処理に対しては無力です。

割り込み処理に対する排他制御

割り込み処理も聞き慣れていない方もいらっしゃると思うので、ざっくりとご説明しましょう。
実社会で見かける割り込み処理の例は下記のような感じです。

  • 携帯電話のボタンが押された時に自動的に実行される処理
  • 携帯電話にメールまたは通話の着信が入った時に自動的に実行される処理
  • 車のエンジンを掛けた時に自動的に実行される処理
  • 車のクラクションを鳴らした時に自動的に実行される処理
  • 車が衝突した時に自動的に実行される処理(エアバッグを作動させる処理) 等々

ITの世界での表現で説明いたします。

CPUには割り込み線というものが複数本(割り込みの種類に応じた数)つながっています。携帯着信等の割り込みが発生すると割り込み線に信号が入ります。
その後、CPUは実行中のスレッド処理をスレッドの都合などお構いなく割り込み処理を実行するために一時停止させます

このような自分勝手な割り込み処理も世の中の電気機器を制御しているソフトウェアでは割り込みなしでは説明ができない程、必要不可欠なものです。
特に人の命にかかわるような機器(自動車、飛行機等)は割り込み処理の塊といっても過言でないでしょう。

仮にスレッドだけで自動車を動かそうとした時はブレーキを踏んでも、ブレーキを止めるスレッドが実行するまで、止まってくれないでしょう。
かといって、割り込み処理だけソフトウェアを作るということも無理があります。

それぞれ役割があるのです。

  • 割り込み処理 ==>何か事が起こった時、最優先で一瞬だけ実行したい処理を担当。
  • スレッド処理 ==>継続的にコツコツと行い続ける処理を担当

そのため、割り込み処理とスレッド処理間の排他処理を考えることも重要です
では、その方法ですが、下記の2つが考えられます。

  • グローバルな資源へのアクセスはスレッドのみとし、割り込み処理からはアクセスしない。
  • グローバルな資源にスレッドからアクセス中、割り込まれると困る割り込み処理を禁止する。

それぞれみてみましょう。

グローバルな資源へのアクセスはスレッドのみとし、割り込み処理からはアクセスしない。

いいかえると、グローバルな変数へのアクセスはスレッドだけにする。例えば、割り込み処理内でアクセスしたいメモリ上の変数がある場合、仮置き場的な変数に一時的に置き、スレッド側では、定期的にその仮置き場を監視(ポーリングとも言われます)、何か置かれていれば、スレッド側で本来の置き場に移動する。

グローバルな資源にスレッドからアクセス中、割り込まれると困る割り込み処理を禁止する。

具体的にはどのように禁止するの?という疑問があるでしょう。割り込みをアプリケーションから直接、禁止できるのはC言語(やアセンブリ言語)のみです。理由はC言語はポインタという概念がありますようにメモリアドレス操作に長けています。実はハードウェアにもIOアドレスというメモリアドレスに似たものがCPUを介して割り当てられています。そのおかげでアプリケーションからもIOアドレスを指定することでハードウェアを制御できます。(もう少し具体的にはいいますIOアドレスを介して入出力レジスタ上の値(特定の割り込みを禁止等)を設定することができます。)
ただ、何も考えず割り込み禁止を多様することは、いうまでもなく論外です。全体の処理速度が遅くなるという副作用があるばかりか、全体がまともに機能しないでしょう。
割り込み処理を扱うようなソフト設計を行うことはとても神経をとがらせる必要があるのです。

まとめ

排他制御を扱う場合、慎重にならないと全体の不具合に直結します。
このため、極論をいいますと、新規に設計を行う時、最初から排他制御ありきでなく、排他制御を使わずに済む方法、設計方法が本当にないのかまずはじっくり考えてみましょう

例えて上げますと、

  • 共有資源へのアクセスを特定の唯一無二のスレッドだけに許可する。
  • メッセージ通信が利用可能なOS(iTron等)を選ぶ。等々

次回も引き続き、まだ4回目ですので実務で役に立ちそうな課題をご紹介したいと思います。

もし、何か興味がある課題がありましたら、コメント頂ければうれしいです。

  • B!