難解なマルチスレッド・プログラミングを基礎から解説。まずはその動作原理を理解し、活用すべき場面を見極める。
コンピュータのプログラムは、基本的に1行ずつコードが実行されながら動作する。通常、分岐やループがあっても、プログラム全体は1つの流れになっている。このような一連のプログラムの流れを「スレッド」(Thread:「糸」などの意味)と呼び、1つのスレッドだけからなるプログラムを「シングルスレッドなプログラム」という。たいていのプログラミングでは1つの処理の流れを記述するが、このようなプログラムはシングルスレッドなプログラムに該当する。
一方、プログラムによっては、処理効率を上げるなどの目的で、複数の処理を並行して行うことができる。つまり、1つのプログラムで複数のスレッドを同時に実行することができるのである。このようなプログラムを「マルチスレッド・プログラム」という。プログラムのコード上では、複数個所が同時に実行されている状態となる。
以下にシングルスレッドとマルチスレッドの処理イメージを示した。シングルスレッドでは、処理Aを実行し続けるだけであるが、マルチスレッドの場合、この図のように、処理Aと処理Bを並行して実行することができる。
.NET Frameworkには、このようなマルチスレッド・プログラミングを行う環境やライブラリが標準で用意されており、マルチスレッド・プログラムを簡単に実装できる。これを上手に活用することでアプリケーションのパフォーマンスを向上できる。しかし、マルチスレッドによって、効果的なパフォーマンス向上を行うためにはそれなりの注意が必要である。
例えば、2つの処理を並行して実行できるため、単純にパフォーマンスが2倍になると考えられがちであるが、それは間違いである。スレッドの仕組みを理解し、マルチスレッドでパフォーマンスが向上する場面を見極めてマルチスレッド化を行わないと、逆にパフォーマンスが低下したりするといったことも起こり得る。
また、パフォーマンスの低下以外にも、マルチスレッド・プログラムにはさまざまなリスクが伴うことも認識しなくてはならない。シングルスレッド・プログラムでは起こらないような、いくつかのトラブルが起こり得るからである。
特に危険なのが、デッドロック(すべてのスレッドがそれぞれ別のスレッドの完了を待ち合って、結果的にすべてのスレッドが停止してしまう状態)を起こしてアプリケーションが完全に止まってしまったり、複数のスレッドが1つのデータに対して同時にアクセスすることにより、知らない間にデータの整合性を破壊してしまったりすることである(これらの詳細については次回以降で解説)。さらにやっかいなことに、デバッグ作業でそれらを洗い出すことは非常に難しいのである。
本連載では、.NETにおけるマルチスレッド・プログラミングを基礎から解説するとともに、どのようなときにマルチスレッド・プログラムを使うのが有効か、また堅牢なマルチスレッド・プログラムを作成するにはどのような注意点に気を付けなくてはならないかなどを解説していく。
■プロセスとスレッドの違い
マルチスレッド・プログラムの仕組みの説明に入る前に、プロセスとスレッドについて解説する。Windowsにおけるプロセスとは、実行中のexeファイルであると考えてよいだろう。タスク・マネージャを起動すると、現在動作しているプロセスの一覧を確認できる。
個々のプロセスは基本的には独立していて、Windows OSのマルチタスク機能を利用して、同時並行的に動作する(マルチプロセス)。メディア・プレーヤーで音楽を聴きながら、ワープロで文章を書きつつ、ブラウザでWebサイトにアクセスするということができるのも、メディア・プレーヤーのプログラム、ワープロのプログラム、ブラウザのプログラムがそれぞれ独立したプロセスとして並行動作するからである。
一方、スレッドはプロセスの内部での話である。プロセスがスレッドを含んでいるという関係になり、1つのプロセスは複数のスレッドを持つことができる。そして複数のスレッドが1プロセス内で実行されることをマルチスレッドと呼んでいる。マルチスレッドは、マルチプロセスと同じようにWindows OSのマルチタスク機能を利用して並行動作する。
それでは、複数のプロセスが並行動作するマルチプロセスと、プロセス内で複数のスレッドが並行動作するマルチスレッドではどこが違うのだろうか。一見すると、どちらも処理を並行して行う技術であるが、その違いは、プロセス間では基本的にメモリは共有されず、スレッド間ではメモリが共有されるという点である。つまり、異なるプロセスが同じメモリ上のデータにアクセスすることは基本的にはないが、スレッド間では同じデータに簡単にアクセスできるということである。
このため、同じデータにアクセスしながら並行動作するような複数の処理には、マルチスレッドを使った方がプログラミングは断然楽になる。
また、プロセスに比べて、スレッドの方が消費するリソース(プロセスやスレッドを管理するために必要なデータ)が少なく、新しいプロセスやスレッドを作成する場合には、スレッドの方が起動のオーバーヘッドが小さい。
■マルチスレッドの動作原理
基本的に、CPUは一度に1つの処理しか実行できない。それでも、マルチプロセッサ(マルチCPU)環境であれば、マルチスレッドの動作は容易に想像がつくだろう。このとき各処理(スレッド)は並列(Parallel)に動作している。
しかし、CPUが1つしかなくても、マルチスレッドなプログラムはマルチスレッドとして動作する。この場合には、1つのCPUが1秒間に数百回という速さで、複数の処理を切り替えながら動作し、疑似的に複数のCPUがあるように振る舞うのである。このような切り替え動作を、コンテキストスイッチと呼び、疑似的に複数の処理が同時に走っているように見えることを、並行(Concurrent)に動作しているという。
このように、複数のCPUで処理を分担できれば、すべての処理が終了するまでの処理時間は向上するわけであるが、1つのCPUで並行処理をさせると、疑似的に処理を分担しているように見えるだけであるので、実際にはすべての処理が終了するまでの時間は変わらず、むしろ処理切り替えのために悪化する可能性もある。
上図では、処理Aと処理Bが両方とも終了するのは(3)のマルチCPUによるマルチスレッドが一番早く、(1)のシングルCPUによるシングルスレッドの場合と(2)のシングルCPUによるマルチスレッドの場合は(ほぼ)同じとなる。一方、処理Bの終了する時間に着目すると、(1)と(3)の場合がともに早く、(2)が一番遅い。つまり、(2)のシングルCPUによるマルチスレッドがパフォーマンスとしては、一番遅くなってしまうともいえるのである。
マルチスレッドの主目的はパフォーマンス向上なのであるが、単純に処理をマルチスレッド化するとパフォーマンスを劣化させてしまう場合もあるのだ。実は、ここにプログラムのどの部分をマルチスレッド化するかという判断をするためのキーが隠されている。マルチスレッド・プログラミングでは、以下に解説するように、レスポンス・タイム(応答時間)とスループット(単位時間当たりの処理能力)を明確に意識しなくてはならない。
Copyright© Digital Advantage Corp. All Rights Reserved.