メインメニューを開く

スレッド (コンピュータ)

スレッドthread)とは、CPU利用の単位。プロセスに比べて、プログラムを実行するときのコンテキスト情報が最小で済むので切り替えが速くなる。スレッドは、thread of execution(実行の脈絡)という言葉を省略したものである。

プログラミングの観点からみると、アプリケーションの処理の「実行の脈絡」は1つでないことが多い。例えば即応性が求められるGUIを描画したりユーザーと対話したりするためのイベントループを実行するフローと、処理に時間のかかるネットワークアクセスやファイルI/O、低速ハードウェアとの通信などを実行するフローは完全に分離してしまったほうが都合がよい。これを単一のコンテキスト(シングルスレッド)上で実現しようとするとシグナルタイマーを駆使してコーディングすることになる。あるいは、複数のプロセスに分割してプロセス間通信で協調動作させるという方法もある。しかし、いずれの場合もそれらの機能を使うための余分な、本来のアルゴリズムと関係ないコーディングが必要となる。スレッドを使用したプログラミングは本来のアルゴリズム(アプリケーションロジック)に集中しやすくなり、プログラムの構造が改善されるという効果がある。

スレッドとプロセスとタスク編集

計算機上で並行計算並列計算あるいはマルチタスクといった、複数の処理を同時に実行するためには、計算機およびその上で動作するオペレーティングシステム (OS) が、プロセッサCPU)時間を個々の処理に対して適切に分配・スケジューリングする機能に対応している必要がある。同時に実行する部分を指定することができる、処理の分割の単位として、スレッドプロセスがある。

新たなプロセスを動作させるためには、CPUやメモリ空間などを割り当てる必要がある。それぞれのプロセスは、割り当てられた資源内で独立して動く。通例、システム全体の動作の安定性および安全性の観点から、個々のプロセスのアドレス空間は仮想化・分離される[1]。プロセスごとにメモリ空間が独立しているため、あるプロセスから別のプロセスが参照しているメモリに直接アクセスするようなことはできない。しかし、独立したメモリ空間が不必要な場合では、メモリの利用効率が悪くなってしまう。プログラムによっては、処理ごとに別々の空間にあるメモリを利用するのではなく、単一の空間内のメモリを共有しながら複数の処理を行なう「共有メモリ方式」のほうが、ロジックの実装のしやすさやメモリ効率の面で優れている場合がある。これを可能にするのがスレッドである。

マルチタスクOSにおいて、1つのタスクは、1つ以上のプロセスから構成され、1つのプロセスは、1つ以上のスレッドから構成される。集合で表すと、スレッド プロセス ∈ タスクというようになる。しかし、この関係は環境によって異なる。例えば、リアルタイムOSでは、タスク スレッド、スレッド ∈ プロセス、である。しかし、タスクとプロセスの間に要素関係はない。

スレッドを使うことで、同一プロセス内の複数スレッドを同一メモリ空間上で実行でき、メモリ消費量などが軽減できるようになっている。しかし、このため、マルチスレッド処理のプログラミングにおいては、同じデータを複数のスレッドが同時に書き換えることによる不整合に注意し、排他制御を行う必要がある。 共有ライブラリ使用時には、その共有ライブラリがスレッドセーフリエントラント)になっているかどうか気をつけてプログラミングしなければならない。

また、複数のスレッドが協調動作する際、お互いの処理完了を待ち合わせてデッドロック (deadlock) 状態に陥ることのないよう配慮する必要もある。

ある処理を単一のスレッドのみを用いて動作させる環境もしくは手法をシングルスレッドという。対して、複数のスレッドが同時に動作することをマルチスレッドという。 プログラム(概ねプロセス)の開始時にはメインとなるスレッドが動作する。必要に応じてその他の処理をするスレッドを作り、実行させることもできる。

基本的に1つのCPUコアが、ある瞬間に同時に実行しているのは割り込み処理なども含めて1つのスレッド、1つのプロセス、1つのタスクである。同時マルチスレッディングなどはこの例外となる。

ユーザースレッドとカーネルスレッド編集

ユーザ空間で実装されたスレッド機構をユーザースレッド、特に仮想機械上で動くものをグリーンスレッドと呼ぶ。ユーザースレッドの切り替えは、そのプロセスがユーザー空間で動作中にライブラリ内のスレッドスケジューラが行う。これは非常にオーバーヘッドが小さく、しかも実装が簡単と言える。しかし、ひとつのプロセス内の複数のスレッドは常に1つだけが動作していることになり、マルチプロセッサシステムの恩恵を受けられない。また、あるスレッドがカーネル内で入出力待ちでスリープしてしまうと、全スレッドが入出力待ちとなってしまうという問題があった。すなわち、ユーザースレッドはあくまでもプログラミングの手法としてのみ意味を持ち、性能向上に寄与するものではない。

カーネル空間で実装されたスレッド機構をカーネルスレッドと呼ぶ。カーネルスレッドの切り替えはカーネルが行うため、マルチプロセッサシステムであれば同じプロセス内の複数のスレッドを並行して実行することもでき、どれかひとつのスレッドがスリープしても別のスレッドは処理を続行できる。しかし、カーネルスレッドは単にユーザー空間などのリソースを共有しているだけで、プロセス管理から見ればプロセスとほとんど変わりないため、オーバーヘッド(コンテキストスイッチなど)もプロセス並みとなる。また、カーネルが全スレッドを管理するため、生成可能なスレッド数の制限がきつくなる[2]

ライトウェイトプロセス編集

ライトウェイトプロセスlight-weight processLWP)または軽量プロセスとは、スレッドを複数並行して実行するためのカーネル内の機構。マルチプロセッシングにおいて、ひとつのプロセス内のスレッドを複数個同時に実行する仕組みである。カーネルスレッドとLWPを総称してネイティブスレッドと呼ぶこともある。

LWPは上述の2つの方式を組み合わせたもので、SolarisSVR4.2MPで導入されたスレッド機構である。プログラミング上いくつでもスレッドを生成できるとしても、並列実行できるのはプロセッサ数までである。従って、カーネルスレッド方式のように全スレッドをカーネルが制御するのは無駄が大きい。そこでカーネルはLWPというスレッドを実行するオブジェクトを管理し、LWPが適当なユーザースレッドを選択して実行する。LWPの個数はプロセス当たりの上限(全プロセッサ数+α)が設定されているため無駄が少なくなり、ユーザースレッドはメモリなどが許す限り生成可能となる。また、ユーザースレッド間の切り替えをユーザー空間で行うため、オーバーヘッドはユーザースレッドとカーネルスレッドの中間になる。

LWPはCライブラリ内でスレッド作成の延長で必要に応じて作成される。Cライブラリ内のスレッドスケジューラが、ユーザースレッドとLWPのマッピングを行う。このマッピングを指して、LWP方式を「M対Nスレッド」と呼ぶことがある。つまり、ユーザープロセス内のM個のスレッドとカーネル内のN個のLWPがマッピングを切り換えながら実行されることを意味している。

カーネルはLWPに関するシステムコールを提供しており、Cライブラリがそれを使用する(ユーザーにも公開されているが、一般に直接使うことはほとんどない)。LWPはコンテキストスイッチの対象として扱われるため、LWP方式のオペレーティングシステムではプロセス制御ブロックの一部がLWP毎のデータ構造になっている。

また、LWPとユーザースレッドを固定的に結びつけることもでき、これを「結合スレッド」と呼ぶ(一般のスレッドは「非結合スレッド」)。

ユーザーインターフェイススレッド編集

グラフィカルユーザインタフェースにおいては、デッドロックにまつわる複雑さを回避するためUIを操作するスレッドを1つに統一し、その上でワーカーデザインパターンを採用するという手法がよく採用される。

ライトウェイトスレッド編集

ライトウェイトスレッドlight-weight thread)または軽量スレッドとは、ユーザープロセス空間内で、疑似的にスレッディング動作を行わせることができる概念である。通常のスレッドと区別する場合に「論理スレッド」と呼ぶ場合がある。

ライトウェイトプロセスとは異なり、スタックなどに退避されている「スレッドコンテキスト情報」の、暗黙の切り替えは行われない場合がある。また、タイムスライスによる論理スレッドの切り替えが起きず、任意のタイミング(例:APIの呼び出し時)にのみ、論理スレッドが切り替わる。完全にユーザープロセス空間内で実現され、一般的に論理スレッドコンテキストの情報量は小さいため、論理スレッド切り替えのオーバーヘッドは非常に小さい。

ライトウェイトスレッドとして分類される機構として、コルーチンや、C#/VB.NETなどの.NET言語におけるイテレータブロックが挙げられる。

スレッドライブラリ編集

C言語C++といった初期のプログラミング言語においては、スレッドは当初言語仕様では標準サポートされていなかった。そのため、例えばMicrosoft WindowsではWindows APIのスレッド、POSIX準拠OSではPOSIXスレッド (Pthreads) といったように、プラットフォーム固有のAPIを利用する必要があった。Java.NET Framework/.NET Coreに代表されるように、現代的なほとんどの後発言語およびプラットフォームではスレッドを標準的にサポートしている。

C++11規格ではBoost C++ライブラリをベースとしたスレッドライブラリが標準化された。C11規格でもスレッドライブラリが標準として定義されたが、実装は任意であり必須ではない[3]

スレッドの暗黙的利用編集

マルチスレッドのプログラミングは前述のように、シングルスレッド前提のプログラミングと比べて難易度が高い。マルチスレッドの動作は非決定論的であり、慎重にプログラミングしなければ、タイミングによって発生したりしなかったりする厄介な異常動作や不具合を引き起こすこともある。マルチスレッド環境におけるバグは、しばしば原因特定が困難となる。

一般的なアプリケーションプログラミングでは、スレッドを明示的に起動して利用することは少ない。代わりに、並列処理や並行処理のバックエンドとしてスレッドを暗黙的に利用する、上位レベルのAPIを利用することが多い。例としてOpenMPや、.NETのタスク並列ライブラリ英語版 (TPL) などが挙げられる。Future パターンをサポートするプログラミング環境では、並行処理の実行にスレッドを利用するが、煩雑なスレッドの操作をほとんど意識することなく並行処理を効率的に記述することができる。また、これらはAPI呼び出しのたびにスレッドを起動/終了するのではなく、あらかじめいくつかのスレッドを起動しておいて再利用することのできるスレッドプールを内部で使用していることが多い。上位レベルのAPIを利用することで、オーバーヘッドを低減し、また実行環境のハードウェア構成を意識することなく、環境に適した数のスレッドを活用することが可能となる。

脚注編集

  1. ^ 古いアーキテクチャや組み込み環境では、複数のプロセスがメモリ空間を共有しているシステムもある。例えばWin16などが挙げられる。
  2. ^ Microsoft Windowsでは、個々のスレッドに割り当てられるスタックサイズの関係上、1プロセスが生成できるスレッドの数は既定で2,048までとなっている。CreateThread function (processthreadsapi.h) | Microsoft Docs
  3. ^ スレッドサポートライブラリ - cppreference.com

関連項目編集