疑似マルチタスクとプリエンプティブ

図4●プリエンプティブ方式と疑似マルチタスク。
プリエンプティブ方式では,OSが強制的にアプリケーション・プロセスから実行権を取り上げる。これに対し疑似マルチタスクでは,アプリケーションが明示的に処理権を放棄しなければ,ずっと処理を継続できる

 ここまで説明してきたマルチタスクの実現方式は,実は「プリエンプティブ(先取り)」方式と呼ばれるものである。アプリケーション・ソフトを作るときに,マルチタスクできるように特に意識しなくてもよいので,まっとうなマルチタスクOSであればすべてこの方式だと思ってよい。プリエンプティブという言葉は,各プロセスの処理をOSが“取り上げる”ことを意味している〔図4[拡大表示] (a)〕。

 注意しなければならないのは,Windows 3.1やバージョン9以前のMacintosh OSのマルチタスク方式である。これらは,「疑似マルチタスク」あるいは「協調型マルチタスク」と呼ばれる方式を採用している。なぜ「疑似」なのかというと,マルチタスクとして動作させるにはOSだけでは完遂できないからである。OSの機能としては「疑似」なのだ。また,アプリケーションが協調的に振る舞うので「協調型」なのである〔図4[拡大表示](b)〕。

 例えばWindowsのプログラムでは,前号で説明したように「メッセージを受け取るループ」というものが存在する。ループは大きく,メッセージを受け取ってメッセージ・ハンドラに引き渡す部分と,メッセージ・ハンドラに分かれている。例えば前者の場合,「メッセージ・ハンドラに引き渡す処理」というのはOSの役割である。このときにアプリケーションは一度OSに処理を返すことになる。またメッセージ・ハンドラはOSから呼び出され,処理が終了すると自動的にOSに処理が戻る。このような構造になっているから,Windows 3.1でもマルチタスクしているように見えるわけだ。

手軽な「スレッド」の登場

 ここまでは「プロセス」にCPUの実行権が割り当てられると説明してきたが,Windows 2000/XPなどマイクロカーネルに基づくOSでは「スレッド」を単位にCPUの実行権が割り当てられる。スレッドは別名,「軽量プロセス」とも呼ばれるものである。プロセスはCPUの実行権だけでなく,論理メモリ空間,仮想I/O空間なども割り当てられる。別の言い方をすれば,プロセスを切り替える際にはCPUの状態だけでなく,メモリやI/O空間なども切り替えなければならないのだ。したがってプロセス切り替えにはそれなりの時間がかかる。

図5●プロセスとスレッドの関係。
プロセスがメモリ,CPU,周辺装置などさまざまなコンピュータ資源を割り当てる単位だとすると,スレッドはCPU時間を割り当てる単位ということになる。したがって複数のスレッドが一つのプロセスの中に存在していても構わない

 また,複数の処理を連携させたいときには,たいていの場合その二つの処理の間でデータを共有できる仕組みが必要である。しかし異なるプロセス間では,手軽にアクセスできるメモリ空間というものなどは原則として存在しない。OSに依頼して共有データ領域を作り,アクセスを依頼すると言った手間をかけなければならない。

 そこでもっと手軽に並行処理を実装できるようにしたものが,スレッドである。スレッドは一つのプロセスの中に複数存在し得るもので,OSが処理時間を割り当てる単位である(図5[拡大表示] )。プロセスを「コンピュータ・リソースを割り当てる単位」と考えれば,スレッドはその中でCPUリソース(=実行時間)を割り当てる単位ということになる。プログラム実行時には一つのスレッドしか存在しないが,プログラム中でスレッドを作成すれば,複数スレッドが同じプロセスの中で動作することになる。

 もちろんスレッドを使ったプログラミングは,プロセスに比べ危険でもある。他のスレッドが予想外にデータを書き換えていることがあり得るからだ。プロセスであれば,データが予想外に書き換えられることはない。メモリ空間がそれぞれ独立しており,保護されているからだ。だがスレッドであれば,同じメモリ空間に存在するので何でもできてしまう。このため複数スレッドが共有するデータに関しては,細心の注意を払ってプログラムする必要がある。

図6●ファイバの概念。
アプリケーションがスレッドをうまくスケジューリングしたいときなどに使う。ただしよく考えられたマルチスレッド・アプリケーションであれば,ファイバを使う必要はない

 Windowsファミリにはこのほか,「ファイバ」と呼ぶスレッドよりも細かいスケジュールの単位がある(図6[拡大表示])。スレッドの中で複数のファイバを生成できる。ただしスレッドの中にある複数のファイバのどれを実行するかは,アプリケーションが明示的に切り替えてやる必要がある。「ConvertThreadToFibers」というAPIによって,ファイバの利用が始まり,「CreateFiber」によってファイバが作られていく。これはスレッドのようにOSによってスケジューリングされるものではなく,アプリケーションが複数の処理をスケジューリングしたい場合に使うものである注3)。

「なるほどぉ。だからコンピュータって,複数の仕事を同時にこなしているんですね。あれ?さっきまで順調に動いていたのに,スレッドが止まっちゃった」
「リソース競合して,デッドロックしてんじゃないのかな」
「デッドロック?」
「マルチタスクの落とし穴,だね」

(北郷 達郎、八木 玲子)