複数のプロセスやスレッドを連携動作させるプログラムを記述する際に陥りやすいトラブルが,デッドロックである。デッドロックは簡単に言ってしまえば,プロセスまたはスレッドが占有するリソースが競合したときに発生する。

 マルチタスクのアプリケーションとはいえ,実際のI/Oなどコンピュータのリソースは限られている。メモリやI/Oなどを別のプロセスが利用している場合は,その処理完了を待たないと使えない。例えばプロセスAがディスクに書き込みをしているときに,別のプロセスが強引にファイルを読み出そうとしてヘッドの位置を勝手に動かしたら,ファイル・システムがグチャグチャになってしまう。リソースの共有排他制御が必要なわけだ。

排他制御のフラグの代わりに使える

リスト1●ミューテックス(Mutex:mutual exclusion)の利用例。
C#を使って表現した

 最も単純なやり方が,「使用中」の標識を立てておくこと。これを実現する仕組みを「ミューテックス」と呼ぶ。ミューテックスは同時にリソースを利用できるスレッドを一つだけに限定するための仕組みである。

 プログラム中では,ある名前を付けた「ミューテックス・オブジェクト」を生成し,そのオブジェクトを持っているプロセスが操作権を持つ,という形になる(リスト1[拡大表示])。こう書いてしまうと単なるフラグとあまり変らないように見えるが,オブジェクトの取得と解放をプログラムで管理しなくて済む分使いやすくなっている。また二つのスレッドがそのリソースの空き待ちをするような場合の調停役になるというメリットもある。ただこれはほかのリソース同期メカニズムも同様だが,ミューテックスを使わずにアクセスしようとすることを禁止できるわけではない。

 ミューテックスに似たものとして,「セマフォ」がある。これは限定された個数のスレッドが同時に利用できるリソースに対して使うものである。いわば,アクセス・カウンタ付きのミューテックスである。

クリティカル・セクションを定義する

 もう一つよく用いられる同期メカニズムに,「クリティカル・セクション」がある。スレッド間で共有する変数領域へのアクセスをする部分を保護するものである。プログラム上は「ここからクリティカル・セクションに入ります」と宣言し,終了したときに「クリティカル・セクションを出ました」と宣言する形になる。ちなみに.NET Frameworkでは「Monitor」という特殊なオブジェクトを使う(リスト2[拡大表示])。

 Javaではこういった共有排他制御の機能として「Synchronizedメソッド」と「Synchronized文」を用意している。

 メソッド名の前に「Synchronized」を付けるとSynchronizedメソッドとなる。この場合,同じクラスにあるすべてのSynchronizedメソッドは,一度に一つのスレッドしか実行されない(図7[拡大表示])。クラスに存在するデータ領域に対し排他制御する場合に有効である。Synchronized文の方は,表記はともかく.NET FrameworkにおけるMonitorと同じものだと思えばいいだろう。なおJavaの場合,ミューテックスやセマフォに相当するクラスは用意されていない。Synchronizedを使うことにより実装可能である。

リスト2●クリティカル・セクションの利用例
 
図7●Synchronizedメソッド。
Javaでは同時に実行するメソッドを一つに限定できる。こうすることにより,共有するリソースに対するアクセスの共有排他制御が可能になる。基本的にオブジェクト単位で,メソッド名の前にキーワードSynchronizedを付けたものが該当する

複数のリソースをロックすると
デッドロックが発生する

図8●デッドロックが発生する理由。
複数のリソースを利用するメソッドが複数存在する場合,個々のリソースに対する操作権をそれぞれが取得するとどちらも実行できない状態になる。この状態を回避するには,権利を取得する順番を定めたり,ある時間すべてのリソースが確保出来ない場合にリソースのロックを解放するといったメカニズムを組み込んでおく必要がある

 以上のような同期メカニズムを使った際に問題となるのが,デッドロックである。デッドロックとは,複数のリソースを使うスレッドがそれぞれ,リソースの所有権を主張して譲らずないため動作が停止してしまう現象を指す(図8[拡大表示])。スレッド1がリソースAにアクセスする権利を取得し,もう一つのスレッド2がリソースBにアクセスする権利を取得したとする。このときスレッド1はリソースBが解放されるのを待ち,スレッド2がリソースAの解放を待ってしまうと,どちらも動けなくなってしまう。

 この状態を回避するには,リソースを取得する順番を決めておいたり,一定時間待たされた場合にはリソースを解放するといった処理を組み込んでおく必要がある。

 実はデッドロックという現象は,アプリケーションだけで発生するわけではない。むしろOSのリソース管理の方がやっかいである。単一スレッドの独立したアプリケーションが複数あれば,デッドロックは発生するからだ。OSでは,リソースやプロセスの状態を監視してデッドロックを回避するメカニズムが盛り込まれている。

「なんでも一つ,ていうのは比較的簡単なんだ。でも二つになるととたんに問題が難しくなる。三つ以上になると,さらに大変。三体問題なんて解けないしね」
「え?三体問題?」
「太陽と地球と月みたいな三つの物体の重力による相互作用を,数学的にきっちり解析して求めることはできない,ということ。それはともかくとして,OSを作る人にとっても,ユーザやタスクがマルチになるととたんに難しい話になる。それはアプリケーションでもやっぱり同じなんだ」
「そうですね。気にしなければいけないことが,途端に増えましたもんね」
「でもまあ,大事なのはスレッド・セーフな作りにすることかな。あとはロジックを独立させるというマルチプロセスの特性をどう生かすかだね」

(北郷 達郎、八木 玲子)