スレッド (コンピュータ)
スレッド(thread)とは、コンピュータプログラムにおいて特定の処理を行うための一貫性のある命令の流れのことであり、プロセッサ利用の最小単位[1]。プロセスは少なくとも1つ以上のスレッドを含む。一般的に各プロセスには独立した仮想アドレス空間が割り当てられるが、プロセス内のスレッド群はアドレス空間を共有する。そのためプログラムを実行するときのコンテキスト情報が最小で済み、同じプロセス内でスレッドを切り替える際はアドレス空間の切り替えが不要となるので、切り替えが高速になる[2]。スレッドは、thread of execution(実行の脈絡)という言葉を省略したものである。複数のスレッドを生成して個々に処理を割り当てて実行させることで、並行処理による応答性の向上などを実現でき、さらにマルチコアプロセッサを複数のスレッドによって活用することで、並列処理による実行時間の短縮などを実現できる(これらの手法をマルチスレッドプログラミングと呼ぶ)。
プログラミングの観点からみると、アプリケーションの処理の「実行の脈絡」は1つでないことが多い。例えば即応性が求められるGUIを描画したりユーザーと対話したりするためのイベントループを実行するフローと、処理に時間のかかるネットワークアクセスやファイルI/O、低速ハードウェアとの通信などを実行するフローは完全に分離してしまったほうが都合がよい。これを単一のコンテキスト(シングルスレッド)上で実現しようとするとシグナルやタイマーを駆使してコーディングすることになる。あるいは、複数のプロセスに分割してプロセス間通信で協調動作させるという方法もある。しかし、いずれの場合もそれらの機能を使うための余分な、本来のアルゴリズムと関係ないコーディングが必要となる。スレッドを使用したプログラミングは本来のアルゴリズム(アプリケーションロジック)に集中しやすくなり、プログラムの構造が改善されるという効果がある。
スレッドとプロセスとタスク
[編集]計算機上で並行計算/並列計算あるいはマルチタスクといった、複数の処理を同時に実行するためには、計算機およびその上で動作するオペレーティングシステム (OS) が、プロセッサ(CPU)時間を個々の処理に対して適切に分配・スケジューリングする機能に対応している必要がある。同時に実行する部分を指定することができる、処理の分割の単位として、スレッドとプロセスがある。
新たなプロセスを動作させるためには、CPUやメインメモリ上のアドレス空間などの計算資源(リソース)を割り当てる必要がある。それぞれのプロセスは、割り当てられた資源内で独立して動く。通例、システム全体の動作の安定性および安全性の観点から、個々のプロセスは実際のメモリ上の位置を指す物理アドレス空間を直接使用するのではなく、OSによって仮想化・分離された仮想アドレス空間を使用する[3][注釈 1]。プロセスごとにメモリ空間が独立しているため、あるプロセスから別のプロセスが参照しているメモリに直接アクセスするようなことはできない。しかし、独立したメモリ空間が不必要な場合では、メモリの利用効率が悪くなってしまう。プログラムによっては、処理ごとに別々の空間にあるメモリを利用するのではなく、単一の空間内のメモリを共有しながら複数の処理を行なう「共有メモリ方式」のほうが、ロジックの実装のしやすさやメモリ効率の面で優れている場合がある。これを可能にするのがスレッドである。
マルチタスクOSにおいて、1つのタスクは、1つ以上のプロセスから構成され、1つのプロセスは、1つ以上のスレッドから構成される。集合で表すと、スレッド ∈ プロセス ∈ タスクというようになる。しかし、この関係は環境によって異なる。例えば、リアルタイムOSでは、タスク ≒ スレッド、スレッド ∈ プロセス、である。しかし、タスクとプロセスの間に要素関係はない。
スレッドを使うことで、同一プロセス内の複数スレッドを同一メモリ空間上で実行でき、メモリ消費量などが軽減できるようになっている。しかし、このため、マルチスレッド処理のプログラミングにおいては、同じデータを複数のスレッドが同時に書き換えることによる不整合に注意し、排他制御を行う必要がある。 共有ライブラリ使用時には、その共有ライブラリがスレッドセーフ(リエントラント)になっているかどうか気をつけてプログラミングしなければならない。
また、複数のスレッドが協調動作する際、お互いの処理完了を待ち合わせてデッドロック (deadlock) 状態に陥ることのないよう配慮する必要もある。
ある処理を単一のスレッドのみを用いて動作させる環境もしくは手法をシングルスレッドという。対して、複数のスレッドが同時に動作することをマルチスレッドという。 プログラム(概ねプロセス)の開始時にはメインとなるスレッドが動作する。必要に応じてその他の処理をするスレッドを作り、実行させることもできる。
基本的なモデルとして、1つのCPUコアがある瞬間に同時に実行しているのは、割り込み処理なども含めて1つのスレッド、1つのプロセス、1つのタスクであるとする。なお、あるスレッドの実行時に余ったCPU資源を別のスレッドに割り当てる技術が同時マルチスレッディング (SMT) であり、SMTが動作しているとき、内部的な物理CPUコアとしては2つ以上のスレッドを同時に実行していることになるが、アプリケーションソフトウェアの観点では、SMTによって生み出される論理CPUコア(論理プロセッサ、見せかけのコア)がある瞬間に実行できるスレッドは1つである。
ユーザースレッドとカーネルスレッド
[編集]ユーザ空間で実装されたスレッド機構をユーザースレッド、特に仮想機械上で動くものをグリーンスレッドと呼ぶ。ユーザースレッドの切り替えは、そのプロセスがユーザー空間で動作中にライブラリ内のスレッドスケジューラが行う。これは非常にオーバーヘッドが小さく、しかも実装が簡単と言える。しかし、ひとつのプロセス内の複数のスレッドは常に1つだけが動作していることになり、マルチプロセッサシステムの恩恵を受けられない。また、あるスレッドがカーネル内で入出力待ちでスリープしてしまうと、全スレッドが入出力待ちとなってしまうという問題があった。すなわち、ユーザースレッドはあくまでもプログラミングの手法としてのみ意味を持ち、性能向上に寄与するものではない。
カーネル空間で実装されたスレッド機構をカーネルスレッドと呼ぶ。カーネルスレッドの切り替えはカーネルが行うため、マルチプロセッサシステムであれば同じプロセス内の複数のスレッドを並行して実行することもでき、どれかひとつのスレッドがスリープしても別のスレッドは処理を続行できる。しかし、カーネルスレッドは単にユーザー空間などのリソースを共有しているだけで、プロセス管理から見ればプロセスとほとんど変わりないため、オーバーヘッド(コンテキストスイッチなど)もプロセス並みとなる[要出典]。また、カーネルが全スレッドを管理するため、生成可能なスレッド数の制限がきつくなる[注釈 2]。
ライトウェイトプロセス
[編集]ライトウェイトプロセス(light-weight process、LWP)または軽量プロセスとは、スレッドを複数並行して実行するためのカーネル内の機構。マルチプロセッシングにおいて、ひとつのプロセス内のスレッドを複数個同時に実行する仕組みである。カーネルスレッドとLWPを総称してネイティブスレッドと呼ぶこともある。
LWPは上述の2つの方式を組み合わせたもので、SolarisやSVR4.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規格でもスレッドライブラリが標準として定義されたが、実装は任意であり必須ではない[7]。
スレッドの暗黙的利用
[編集]マルチスレッドのプログラミングは前述のように、シングルスレッド前提のプログラミングと比べて難易度が高い。マルチスレッドの動作は非決定論的であり、慎重にプログラミングしなければ、タイミングによって発生したりしなかったりする厄介な異常動作や不具合を引き起こすこともある。マルチスレッド環境におけるバグは、しばしば原因特定が困難となる。
一般的なアプリケーションプログラミングでは、スレッドを明示的に起動して利用することは少ない。代わりに、並列処理や並行処理のバックエンドとしてスレッドを暗黙的に利用する、上位レベルのAPIを利用することが多い。例としてOpenMPや、.NETのタスク並列ライブラリ (TPL) などが挙げられる。Futureをサポートするプログラミング環境では、並行処理の実行にスレッドを利用するが、煩雑なスレッドの操作をほとんど意識することなく並行処理を効率的に記述することができる。また、これらはAPI呼び出しのたびにスレッドを起動/終了するのではなく、あらかじめいくつかのスレッドを起動しておいて再利用することのできるスレッドプールを内部で使用していることが多い。上位レベルのAPIを利用することで、オーバーヘッドを低減し、また実行環境のハードウェア構成を意識することなく、環境に適した数のスレッドを活用することが可能となる。しかし、スレッドを暗黙利用する場合でも、共有資源に複数のスレッドから同時アクセスする場合の排他制御などに関しては、依然として配慮が必要であることが多い。
脚注
[編集]注釈
[編集]出典
[編集]- ^ “スレッドとは - IT用語辞典”. IT用語辞典 e-Words. 2022年6月15日閲覧。
- ^ マルチスレッドの基本概念 (マルチスレッドのプログラミング) | Oracle
- ^ 仮想アドレス(論理アドレス)とは - 意味をわかりやすく - IT用語辞典 e-Words
- ^ CreateThread function (processthreadsapi.h) | Microsoft Docs
- ^ Virtual memory in 32-bit version of Windows - Windows Server | Microsoft Learn
- ^ Pushing the Limits of Windows: Processes and Threads - Microsoft Community Hub
- ^ スレッドサポートライブラリ - cppreference.com