第1回 マルチスレッドはこんなときに使う連載.NETマルチスレッド・プログラミング入門

難解なマルチスレッド・プログラミングを基礎から解説。まずはその動作原理を理解し、活用すべき場面を見極める。

» 2005年03月12日 00時00分 公開
連載.NETマルチスレッド・プログラミング入門
Insider.NET

 

「連載.NETマルチスレッド・プログラミング入門」のインデックス

連載目次

シングルスレッドとマルチスレッド

 コンピュータのプログラムは、基本的に1行ずつコードが実行されながら動作する。通常、分岐やループがあっても、プログラム全体は1つの流れになっている。このような一連のプログラムの流れを「スレッド」(Thread:「糸」などの意味)と呼び、1つのスレッドだけからなるプログラムを「シングルスレッドなプログラム」という。たいていのプログラミングでは1つの処理の流れを記述するが、このようなプログラムはシングルスレッドなプログラムに該当する。

 一方、プログラムによっては、処理効率を上げるなどの目的で、複数の処理を並行して行うことができる。つまり、1つのプログラムで複数のスレッドを同時に実行することができるのである。このようなプログラムを「マルチスレッド・プログラム」という。プログラムのコード上では、複数個所が同時に実行されている状態となる。

 以下にシングルスレッドとマルチスレッドの処理イメージを示した。シングルスレッドでは、処理Aを実行し続けるだけであるが、マルチスレッドの場合、この図のように、処理Aと処理Bを並行して実行することができる。

シングルスレッドとマルチスレッドの処理イメージ
マルチスレッドでは、1つのアプリケーションにおいて同時に複数の処理が実行可能だ。

 .NET Frameworkには、このようなマルチスレッド・プログラミングを行う環境やライブラリが標準で用意されており、マルチスレッド・プログラムを簡単に実装できる。これを上手に活用することでアプリケーションのパフォーマンスを向上できる。しかし、マルチスレッドによって、効果的なパフォーマンス向上を行うためにはそれなりの注意が必要である。

 例えば、2つの処理を並行して実行できるため、単純にパフォーマンスが2倍になると考えられがちであるが、それは間違いである。スレッドの仕組みを理解し、マルチスレッドでパフォーマンスが向上する場面を見極めてマルチスレッド化を行わないと、逆にパフォーマンスが低下したりするといったことも起こり得る。

 また、パフォーマンスの低下以外にも、マルチスレッド・プログラムにはさまざまなリスクが伴うことも認識しなくてはならない。シングルスレッド・プログラムでは起こらないような、いくつかのトラブルが起こり得るからである。

 特に危険なのが、デッドロック(すべてのスレッドがそれぞれ別のスレッドの完了を待ち合って、結果的にすべてのスレッドが停止してしまう状態)を起こしてアプリケーションが完全に止まってしまったり、複数のスレッドが1つのデータに対して同時にアクセスすることにより、知らない間にデータの整合性を破壊してしまったりすることである(これらの詳細については次回以降で解説)。さらにやっかいなことに、デバッグ作業でそれらを洗い出すことは非常に難しいのである。

 本連載では、.NETにおけるマルチスレッド・プログラミングを基礎から解説するとともに、どのようなときにマルチスレッド・プログラムを使うのが有効か、また堅牢なマルチスレッド・プログラムを作成するにはどのような注意点に気を付けなくてはならないかなどを解説していく。

1つのCPUでも複数の処理を同時に実行するマルチスレッドの仕組み

■プロセスとスレッドの違い

 マルチスレッド・プログラムの仕組みの説明に入る前に、プロセスとスレッドについて解説する。Windowsにおけるプロセスとは、実行中のexeファイルであると考えてよいだろう。タスク・マネージャを起動すると、現在動作しているプロセスの一覧を確認できる。

 個々のプロセスは基本的には独立していて、Windows OSのマルチタスク機能を利用して、同時並行的に動作する(マルチプロセス)。メディア・プレーヤーで音楽を聴きながら、ワープロで文章を書きつつ、ブラウザでWebサイトにアクセスするということができるのも、メディア・プレーヤーのプログラム、ワープロのプログラム、ブラウザのプログラムがそれぞれ独立したプロセスとして並行動作するからである。

 一方、スレッドはプロセスの内部での話である。プロセスがスレッドを含んでいるという関係になり、1つのプロセスは複数のスレッドを持つことができる。そして複数のスレッドが1プロセス内で実行されることをマルチスレッドと呼んでいる。マルチスレッドは、マルチプロセスと同じようにWindows OSのマルチタスク機能を利用して並行動作する。

 それでは、複数のプロセスが並行動作するマルチプロセスと、プロセス内で複数のスレッドが並行動作するマルチスレッドではどこが違うのだろうか。一見すると、どちらも処理を並行して行う技術であるが、その違いは、プロセス間では基本的にメモリは共有されず、スレッド間ではメモリが共有されるという点である。つまり、異なるプロセスが同じメモリ上のデータにアクセスすることは基本的にはないが、スレッド間では同じデータに簡単にアクセスできるということである。

プロセスとスレッド
プロセスは複数のスレッドという処理単位を内部に持つことができる。プロセス同士は独立しており、スレッド間ではメモリを共有する。

 このため、同じデータにアクセスしながら並行動作するような複数の処理には、マルチスレッドを使った方がプログラミングは断然楽になる。

 また、プロセスに比べて、スレッドの方が消費するリソース(プロセスやスレッドを管理するために必要なデータ)が少なく、新しいプロセスやスレッドを作成する場合には、スレッドの方が起動のオーバーヘッドが小さい。

■マルチスレッドの動作原理

 基本的に、CPUは一度に1つの処理しか実行できない。それでも、マルチプロセッサ(マルチCPU)環境であれば、マルチスレッドの動作は容易に想像がつくだろう。このとき各処理(スレッド)は並列(Parallel)に動作している。

 しかし、CPUが1つしかなくても、マルチスレッドなプログラムはマルチスレッドとして動作する。この場合には、1つのCPUが1秒間に数百回という速さで、複数の処理を切り替えながら動作し、疑似的に複数のCPUがあるように振る舞うのである。このような切り替え動作を、コンテキストスイッチと呼び、疑似的に複数の処理が同時に走っているように見えることを、並行(Concurrent)に動作しているという。

シングルCPUとマルチCPUにおけるマルチスレッドの比較

 このように、複数のCPUで処理を分担できれば、すべての処理が終了するまでの処理時間は向上するわけであるが、1つのCPUで並行処理をさせると、疑似的に処理を分担しているように見えるだけであるので、実際にはすべての処理が終了するまでの時間は変わらず、むしろ処理切り替えのために悪化する可能性もある。

 上図では、処理Aと処理Bが両方とも終了するのは(3)のマルチCPUによるマルチスレッドが一番早く、(1)のシングルCPUによるシングルスレッドの場合と(2)のシングルCPUによるマルチスレッドの場合は(ほぼ)同じとなる。一方、処理Bの終了する時間に着目すると、(1)(3)の場合がともに早く、(2)が一番遅い。つまり、(2)のシングルCPUによるマルチスレッドがパフォーマンスとしては、一番遅くなってしまうともいえるのである。

 マルチスレッドの主目的はパフォーマンス向上なのであるが、単純に処理をマルチスレッド化するとパフォーマンスを劣化させてしまう場合もあるのだ。実は、ここにプログラムのどの部分をマルチスレッド化するかという判断をするためのキーが隠されている。マルチスレッド・プログラミングでは、以下に解説するように、レスポンス・タイム(応答時間)とスループット(単位時間当たりの処理能力)を明確に意識しなくてはならない。

マルチスレッド・プログラミングの目的はパフォーマンス向上

 マルチスレッド処理の目的を、ツリー状に図解してみた。マルチスレッドの目的であるパフォーマンスの向上を詳しく分類すると、レスポンス・タイムとスループットの向上の2つに分類することができる。

マルチスレッド・プログラミングを行う目的と手段
マルチスレッドで実現するパフォーマンス向上は、レスポンス・タイムの向上とスループットの向上に分けることができる。

 以下では、レスポンス・タイム向上とスループット向上について、それぞれ詳しく見ていく。

■レスポンス・タイムの向上

 マルチスレッド・プログラミングでパフォーマンスを向上できる効果の高いパターンが、レスポンス・タイムの向上である。レスポンス・タイムとは、処理のリクエストを出してから、最初の反応が返ってくるまでの時間である。例えば、マウスをクリックしてから、その反応を画面上で認識できるまでの時間ということになる。この場合、レスポンス・タイムの向上はユーザー・インターフェイスの使い勝手の向上に直結する。

 マルチスレッドで実現できるレスポンス・タイムの向上は、重い処理を実行しているときでも、ほかの処理に対して即時に反応できるようにするということである。ここで重い処理とは、時間のかかる計算や、ネットワークによる通信、ディスクへのアクセスなどである。即時に反応するべき処理とは、ユーザー・インターフェイスからの入力や、サーバであればほかのクライアントからの要求などである。このような場面でマルチスレッドを用いないとすると、重い処理を実行している間、ユーザー・インターフェイスやクライアント・アプリケーションはフリーズしてしまうことになる。

 それぞれ具体例を挙げてみよう。例えば、ブラウザはHTMLデータをWebサーバからネットワークを通してダウンロードし、解析表示するプログラムである。当然、ネットワーク越しにデータをダウンロードするには時間がかかる。ほとんどのブラウザでは、実は、このネットワーク越しにデータをダウンロードするという部分は別スレッドで動作している。そのため、HTMLデータのダウンロードが完了し、目的のWebが完全に表示される前でも、[戻る]ボタンや[中止]ボタンはクリック可能であり、ブラウザはユーザーの操作に即座に反応する。もし、シングルスレッドでプログラミングされたブラウザであれば、Webサーバにアクセスを開始したときから、データが完全にダウンロードされるまで、ユーザーからのマウスの入力をまったく受け付けなくなってしまうだろう。

 プログレスバーを使用するときも、マルチスレッド化が必要となる。プログレスバーは、ある重い処理の進行状況を示すものであるが、重い処理が終了するまで、そのステータスを更新しないのでは意味がない。プログラムが何か重い処理をしている間でも、逐次プログレスバーの表示状態を更新するためには、重い処理とプログレスバーの更新処理を別々のスレッドで動作させる必要がある。

マルチスレッドにより表示更新を行うプログレスバー
マルチスレッドによって動作する、ユーザー・インターフェイスの代表例。

 サーバ上でのプログラムの例も考えてみよう。Webサーバなどのサーバ・プログラムは、複数のクライアントからのアクセスを同時に受け付けることができるようにプログラムされている。ここでもマルチスレッドが役に立つ。

 サーバ・プログラムがシングルスレッドの場合には、一度に1つのクライアントからの処理しか受け付けられないことになってしまう。もし、あるクライアントが時間のかかる処理をリクエストしたならば、その処理が終わるまでは、ほかのクライアントは待たされることになるだろう。これをマルチスレッドにすることによって、各クライアントへの処理時間を平準化することができるようになり、複数クライアントのリクエストをバランス良く処理できるようになる。

マルチスレッドによる複数クライアントへの応答
マルチスレッドにより、複数のクライアントからのリクエストをバランス良く処理することができる。

■スループットの向上

 マルチスレッドによって、遅いデバイスに対するCPU待機時間の利用や、マルチプロセッサ環境を効率よく利用でき、システム全体のスループットも向上させることができる。

 前者のパターンでは、CPUが遅いデバイスの処理結果を待っているときに生じる無駄な時間をマルチスレッド化によって活用する。遅いデバイスとはディスクやネットワークなどである。これらはメモリなどに比べるとアクセスに時間がかかる。

 例えば、ディスク上にあるすべてのファイルをリストアップするような処理を行うとき、ディスクにアクセスしている時間は非常に長くなるが、その間CPUでの処理はほとんどない。このようなCPUの空き時間がパフォーマンス向上の余地になる。ディスクへのアクセス処理を別スレッドで実行することにより、メインのスレッドではCPUを利用したほかの処理を行うことができるようになり、全体のスループットは向上する。

 ネットワークへのアクセスも、ディスクほどではないが時間がかかる。先ほど、ダウンロードのためのスレッドと、ユーザー・インターフェイスのためのスレッドを持つブラウザの例を挙げたが、たいていのブラウザでは、ダウンロード用のスレッドは1つではなく複数個となっている(そしてまた、たいていのWebサーバはマルチスレッドにより複数アクセスを同時に処理できる)。これにより、HTMLファイルのダウンロードと、そのページに含まれているいくつかの画像ファイルのダウンロードが同時に処理でき、ページ全体が表示されるまでの時間を短くしている。

 もう1つのパターンがマルチプロセッサの効率的利用である。前述したように、ハードウェアの進化により1台のコンピュータに複数のCPUを搭載するマルチプロセッサが登場してきた。マルチスレッドを用いて明示的に複数の処理を走らせることによって、マルチプロセッサの並行処理の恩恵を受け、その処理性能を十分に発揮できるようになる。

マルチスレッド化で速度向上が期待できる場面は限られている

 マルチスレッド化によってアプリケーションを高速化できるとよくいわれているが、解説してきたように、実は高速化できる場面もその効果も限られていることが分かっていただけただろう。

 レスポンス・タイムの向上は、最も効果が大きいマルチスレッドによる高速化である。ユーザー・インターフェイスがフリーズする場面や、サーバ的な動作により複数のクライアントからのリクエストに応えなくてはならない場合には、ほぼ慣用的に用いられる。注意しなくてはならないのは、レスポンス・タイムの向上の場合、高速化というのは、あくまでユーザーから見た反応についてであり、実際に処理をすべて完了するまでの時間、つまりスループットは良くならないということである。マルチスレッド化のオーバーヘッドにより、スループットはむしろ悪化する。

 スループットの向上が期待できる場面は、さらに限定的である。マルチプロセッサ環境であれば確かにその向上が期待できるが、Windowsでは、よほど高級なサーバ・マシン以外ではシングルCPUであることが普通で、利用できる環境は限られてくる。遅いデバイスに対するI/Oの待ち時間の利用も、使用できる場面は限られてくるであろう。

 このように、どのような場面でもマルチスレッド化すればパフォーマンスが向上するわけではないということに注意しなくてはならない。それと同時にマルチスレッド化は、冒頭で述べたとおり、デッドロックやデータの破壊などが発生する可能性のある、非常にリスクを伴うプログラミング技法である。

 もちろん、パフォーマンスの向上が望める場面ではマルチスレッド化をするべきであるし、使うべき場面でマルチスレッドを使用しないと使い勝手の悪いプログラムとなってしまう。必要となる場面を的確に判断し、慎重にプログラミングを行い、少ないリスクで最大限の効果を上げるようなプログラムを設計しなくてはならない。


 以上、今回はスレッドが動作する仕組みについて説明し、マルチスレッドの目的を分類することにより、マルチスレッド・プログラムが有効となる場合の処理について見てきた。次回からは、.NETでマルチスレッド・プログラムを作成するための具体的なプログラミングについて解説していく。

「連載.NETマルチスレッド・プログラミング入門」のインデックス

連載.NETマルチスレッド・プログラミング入門

Copyright© Digital Advantage Corp. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

4AI by @IT - AIを作り、動かし、守り、生かす
Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。