

コンピュータサイエンスにおいて、実行スレッドとは、スケジューラ(通常はオペレーティングシステムの一部)によって独立して管理できる、プログラムされた命令の最小のシーケンスです。[1]多くの場合、スレッドはプロセスのコンポーネントです。
特定のプロセス内の複数のスレッドは、マルチスレッド機能を介して同時に実行され、メモリなどのリソースを共有しますが、異なるプロセス間ではこれらのリソースは共有されません。特に、プロセス内のスレッドは、実行コード、動的に割り当てられた変数、および非スレッドローカルな グローバル変数の値を常に共有します。
スレッドとプロセスの実装はオペレーティングシステムによって異なります。[2] [ページが必要]
スレッドは、1967年にIBMのバッチ処理オペレーティングシステムOS/360で「タスク」という名前で初めて登場しました。OS/360では、ユーザーに3種類のOS/360制御システム構成が提供され、そのうちの1つが可変タスク数マルチプログラミング(MVT)でした。Saltzer (1966)は、「スレッド」という用語をVictor A. Vyssotskyに帰しています。[3]
Machのスレッド実装は1986年の夏に発表されました。[4] 1987年にリリースされた OS/2 1.0はスレッドをサポートしました。 [5]スレッドを搭載した最初のWindowsバージョンは、1993年にリリースされた Windows NTでした。
2000年代初頭、CPUがマルチコアを利用するようになったため、ソフトウェアアプリケーションにおけるスレッドの使用はより一般的になりました。パフォーマンス向上のためにマルチコアを活用したいアプリケーションでは、マルチコアを活用するために並行処理を採用する必要がありました。[6]
スケジューリングはカーネルレベルまたはユーザーレベルで実行でき、マルチタスクはプリエンプティブまたは協調的に実行できます。これにより、関連する様々な概念が生まれます。
カーネル レベルでは、プロセスには 1 つ以上のカーネル スレッドが含まれ、これらのスレッドはメモリやファイル ハンドルなどのプロセスのリソースを共有します。プロセスはリソースの単位で、スレッドはスケジュールと実行の単位です。カーネル スケジューリングは通常、一様にプリエンプティブに行われますが、まれに協調的に行われることもあります。ユーザー レベルでは、ランタイム システムなどのプロセス自体が複数の実行スレッドをスケジュールできます。これらがErlangのようにデータを共有しない場合は、通常類似してプロセスと呼ばれます[7]。一方、データを共有する場合は、特にプリエンプティブにスケジュールされている場合は、通常(ユーザー) スレッドと呼ばれます。協調的にスケジュールされたユーザー スレッドはファイバーと呼ばれ、異なるプロセスがユーザー スレッドを異なる方法でスケジュールする場合があります。ユーザー スレッドは、カーネル スレッドによってさまざまな方法 (1 対 1、多対 1、多対多) で実行できます。軽量プロセスという用語は、ユーザー スレッド、またはユーザー スレッドをカーネル スレッドにスケジュールするカーネルのメカニズムを指します。
プロセスはカーネル スケジューリングの重量級の単位で、プロセスの作成、破棄、切り替えには比較的コストがかかります。プロセスはオペレーティング システムによって割り当てられたリソースを所有します。リソースには、メモリ (コード用とデータ用の両方)、ファイル ハンドル、ソケット、デバイス ハンドル、ウィンドウ、プロセス制御ブロックなどがあります。プロセスはプロセス分離によって分離されており、ファイル ハンドルや共有メモリ セグメントの継承、同じファイルの共有マッピングなどの明示的な方法 (プロセス間通信を参照) を除き、アドレス空間やファイル リソースを共有しません。プロセスの作成または破棄には、リソースを取得または解放する必要があるため、比較的コストがかかります。プロセスは通常、プリエンプティブにマルチタスク化されており、プロセス切り替えには、キャッシュ フラッシュ (特に、プロセス切り替えによって仮想メモリのアドレス指定が変更され、タグなしトランスレーション ルックアサイド バッファ(TLB)が無効化されてフラッシュされる、という問題( x86の場合に顕著)) により、コンテキスト切り替えの基本コストを超えて比較的コストがかかります。
カーネルスレッドは、カーネル スケジューリングの軽量単位です。各プロセスには、少なくとも 1 つのカーネル スレッドが存在します。プロセス内に複数のカーネル スレッドが存在する場合、それらは同じメモリとファイル リソースを共有します。オペレーティング システムのプロセススケジューラがプリエンプティブである場合、カーネル スレッドはプリエンプティブにマルチタスク化されます。カーネル スレッドは、スタック、プログラム カウンタを含むレジスタのコピー、およびスレッドローカル ストレージ(存在する場合) 以外のリソースを所有しないため、作成および破棄にかかるコストは比較的低くなります。スレッドの切り替えも比較的低くなります。コンテキスト スイッチ (レジスタおよびスタック ポインタの保存と復元) が必要ですが、仮想メモリは変更されないため、キャッシュ フレンドリです (TLB は有効なままです)。カーネルは、CPU の各コアに 1 つ以上のソフトウェア スレッドを割り当てることができ (マルチスレッドのサポート状況に応じて、カーネル自身に複数のソフトウェア スレッドを割り当てることができます)、ブロックされたスレッドをスワップアウトできます。ただし、カーネル スレッドのスワップは、ユーザー スレッドよりもかなり時間がかかります。
スレッドはユーザー空間ライブラリに実装されることがあり、ユーザースレッドと呼ばれます。カーネルはスレッドを認識しないため、ユーザー空間で管理およびスケジュールされます。一部の実装では、マルチプロセッサマシン(M:Nモデル)のメリットを享受するために、複数のカーネルスレッド上にユーザースレッドが実装されています。仮想マシンによって実装されるユーザースレッドは、グリーンスレッドとも呼ばれます。
ユーザースレッドの実装は通常、完全にユーザー空間で行われるため、同一プロセス内のユーザースレッド間のコンテキストスイッチはカーネルとのやり取りを一切必要としないため、非常に効率的です。コンテキストスイッチは、現在実行中のユーザースレッドまたはファイバーが使用するCPUレジスタをローカルに保存し、次に実行するユーザースレッドまたはファイバーに必要なレジスタをロードすることで実行できます。スケジューリングはユーザー空間で行われるため、スケジューリングポリシーをプログラムのワークロード要件に合わせてより簡単に調整できます。
しかし、ユーザースレッド(カーネルスレッドとは対照的に)でブロッキングシステムコールを使用すると、問題が発生する可能性があります。ユーザースレッドまたはファイバーがブロックを伴うシステムコールを実行すると、プロセス内の他のユーザースレッドとファイバーは、システムコールが戻るまで実行できません。この問題の典型的な例は、I/O実行時です。ほとんどのプログラムはI/Oを同期的に実行するように記述されています。I/O操作が開始されると、システムコールが発行され、I/O操作が完了するまで戻りません。その間、プロセス全体がカーネルによって「ブロック」され、実行できなくなります。その結果、同じプロセス内の他のユーザースレッドとファイバーは実行できなくなります。
この問題に対する一般的な解決策(特に多くのグリーンスレッド実装で使用されている)は、プロセス全体ではなく呼び出しスレッドをブロックするインターフェースを実装したI/O APIを提供することです。具体的には、内部的に非ブロッキングI/Oを使用し、I/O操作の実行中に別のユーザースレッドまたはファイバーをスケジュールします。同様の解決策は、他のブロッキングシステムコールにも適用できます。あるいは、同期I/Oやその他のブロッキングシステムコール(特に、ラムダ継続やasync/ awaitプリミティブ[8]を含む非ブロッキングI/O )の使用を避けるようにプログラムを記述することもできます。
ファイバーは、協調的にスケジュールされる、さらに軽量なスケジューリング単位です。実行中のファイバーは、別のファイバーの実行を許可するには明示的に制御を譲る必要があります。そのため、カーネルスレッドやユーザースレッドよりも実装がはるかに簡単です。ファイバーは、同じプロセス内のどのスレッドでも実行するようにスケジュールできます。これにより、アプリケーションは、カーネルスケジューラ(アプリケーションに合わせて調整されていない可能性があります)に頼るのではなく、スケジューリングを自身で管理することで、パフォーマンスを向上させることができます。OpenMP並列プログラミングモデルの研究実装の中には、ファイバーを通じてタスクを実装しているものもあります。[ 9] [10]ファイバーと密接に関連しているのはコルーチンですが、コルーチンは言語レベルの構成要素であるのに対し、ファイバーはシステムレベルの構成要素であるという違いがあります。
スレッドは、従来のマルチタスクオペレーティング システムプロセスとはいくつかの点で異なります。
Windows NTやOS/2などのシステムは、スレッドが安価でプロセスが高価であると言われています。他のオペレーティング システムでは、アドレス空間スイッチのコストを除いてそれほど大きな違いはありません。アドレス空間スイッチは、一部のアーキテクチャ (特にx86 ) では変換ルックアサイド バッファ(TLB) のフラッシュを引き起こします。
スレッドとプロセスの利点と欠点は次のとおりです。
オペレーティングシステムは、プリエンプティブまたは協調的にスレッドをスケジュールします。マルチユーザーオペレーティングシステムでは、コンテキストスイッチを介して実行時間をより細かく制御できるため、通常、プリエンプティブマルチスレッドが優先されます。ただし、プリエンプティブスケジューリングでは、プログラマが予期しない瞬間にスレッドのコンテキストスイッチが発生する可能性があり、ロックコンボイ、優先順位の逆転、またはその他の副作用が発生します。対照的に、協調的マルチスレッドでは、スレッドが実行の制御を放棄するため、スレッドが完了するまで実行されます。これは、協調的にマルチタスク化されたスレッドがリソースを待機してブロックしたり、集中的な計算中に実行の制御を譲らずに他のスレッドを飢えさせたりする場合に問題を引き起こす可能性があります。
2000年代初頭まで、ほとんどのデスクトップコンピュータはシングルコアCPUを1つしか搭載しておらず、ハードウェアスレッドはサポートされていませんでした。しかし、スレッド間の切り替えは、プロセス全体のコンテキストスイッチよりも一般的に速かったため、スレッドは依然としてそのようなコンピュータで使用されていました。2002年、Intelはハイパースレッディングという名前で、Pentium 4プロセッサに同時マルチスレッドのサポートを追加しました。2005年には、IntelはデュアルコアPentium Dプロセッサを発表し、AMDはデュアルコアAthlon 64 X2プロセッサを発表しました。
シングルプロセッサのシステムでは、一般的にタイムスライスによってマルチスレッドが実装されています。つまり、中央処理装置(CPU) が異なるソフトウェア スレッドを切り替えるのです。このコンテキスト切り替えは通常、頻繁に発生するため、ユーザーはスレッドまたはタスクが並行して実行されていると認識します (一般的なサーバー/デスクトップ オペレーティング システムでは、他のスレッドが待機している場合のスレッドの最大タイムスライスは、100~200 ミリ秒に制限されることが多いです)。マルチプロセッサまたはマルチコアシステムでは、複数のスレッドを並行して実行することができ、各プロセッサまたはコアが同時に個別のスレッドを実行します。ハードウェア スレッドを備えたプロセッサまたはコアでは、個別のソフトウェア スレッドを個別のハードウェア スレッドで同時に実行することもできます。
カーネル内のスケジュール可能なエンティティ[11]と1:1で対応するユーザ作成スレッドは、最もシンプルなスレッド実装です。OS /2とWin32は最初からこのアプローチを採用しており、LinuxではGNU Cライブラリがこのアプローチを実装しています( NPTLまたは古いLinuxThreads経由)。このアプローチは、 Solaris、NetBSD、FreeBSD、macOS、iOSでも採用されています。
M :1モデルは、すべてのアプリケーションレベルのスレッドが1つのカーネルレベルのスケジュールされたエンティティにマップされることを意味します。[ 11]カーネルはアプリケーションスレッドを認識しません。このアプローチにより、コンテキストスイッチが非常に高速に実行でき、さらに、スレッドをサポートしない単純なカーネルでも実装できます。ただし、大きな欠点の1つは、マルチスレッドプロセッサまたはマルチプロセッサコンピュータのハードウェアアクセラレーションの恩恵を受けられないことです。同時に複数のスレッドがスケジュールされることはありません。[11]たとえば、スレッドの1つがI/O要求を実行する必要がある場合、プロセス全体がブロックされ、スレッドの利点を活用できません。GNUポータブルスレッドは、状態スレッドと同様に、ユーザーレベルのスレッドを使用します。
M : Nは、 M個のアプリケーションスレッドをN個のカーネルエンティティ[11] 、または「仮想プロセッサ」にマッピングします。これは、カーネルレベル(「1:1」)スレッドとユーザーレベル(「 N :1」)スレッドの妥協案です。一般に、「M : N」スレッドシステムは、カーネルとユーザー空間の両方のコードに変更が必要になるため、カーネルスレッドやユーザースレッドよりも実装が複雑です[説明が必要]。M:N実装では、スレッドライブラリが、利用可能なスケジュール可能なエンティティ上でユーザースレッドをスケジュールする役割を担います。これにより、システムコールが回避されるため、スレッドのコンテキストスイッチが非常に高速になります。ただし、これにより複雑さが増し、優先度逆転の可能性が高くなるだけでなく、ユーザーランドスケジューラとカーネルスケジューラ間の広範かつ高価な調整がなければ、最適ではないスケジューリングが可能になります。
SunOS 4.x は軽量プロセスまたは LWP を実装しました。NetBSD 2.x + とDragonFly BSD はカーネルスレッドとして LWP を実装しています (1:1 モデル)。SunOS 5.2 から SunOS 5.8 および NetBSD 2 から NetBSD 4 は、各カーネルスレッドで 1 つ以上のユーザーレベルスレッドを多重化する 2 レベルモデルを実装しました (M:N モデル)。SunOS 5.9 以降および NetBSD 5 ではユーザースレッドのサポートが削除され、1:1 モデルに戻りました。[12] FreeBSD 5 は M:N モデルを実装しました。FreeBSD 6 は 1:1 と M:N の両方をサポートし、ユーザーは /etc/libmap.conf を使用して特定のプログラムでどちらを使用するかを選択できました。FreeBSD 7 以降では、1:1 がデフォルトになりました。FreeBSD 8 は M:N モデルをサポートしなくなりました。
コンピュータプログラミングにおいて、シングルスレッドとは、一度に1つの命令を処理することです。 [13]変数のセマンティクスとプロセス状態の形式的な分析では、シングルスレッドという用語は「単一のスレッド内でのバックトラック」という意味で異なる意味で使用されることもあり、これは関数型プログラミングコミュニティで一般的です。[14]
マルチスレッドは、主にマルチタスクOSで採用されています。マルチスレッドは、1つのプロセス内で複数のスレッドを共存させることを可能にする、広く普及したプログラミングおよび実行モデルです。これらのスレッドはプロセスのリソースを共有しますが、それぞれ独立して実行できます。スレッドプログラミングモデルは、開発者に同時実行の有用な抽象化を提供します。また、マルチスレッドを1つのプロセスに適用することで、マルチプロセスシステム 上での並列実行を実現することもできます。
マルチスレッドライブラリは、関数を引数として受け取る新しいスレッドを作成するための関数呼び出しを提供する傾向があります。これにより、渡された関数の実行を開始し、関数が返された時点で終了する並行スレッドが作成されます。スレッドライブラリは、データ同期機能も提供しています。
同一プロセス内のスレッドは、同一のアドレス空間を共有します。これにより、同時に実行されるコードは緊密に結合し、 IPCのオーバーヘッドや複雑さなしに、容易にデータを交換できます。しかし、スレッド間でデータを共有する場合、単純なデータ構造であっても、更新に複数のCPU命令を必要とする場合は競合状態が発生しやすくなります。2つのスレッドが同時にデータ構造を更新しようとし、予期せぬ変更が発生する場合があります。競合状態によって引き起こされるバグは、再現や特定が非常に困難になる可能性があります。
これを防ぐため、スレッド化アプリケーション・プログラミング・インターフェース(API)は、データ構造を同時アクセスからロックするためのミューテックスなどの同期プリミティブを提供しています。ユニプロセッサシステムでは、ロックされたミューテックスに遭遇したスレッドはスリープ状態となり、コンテキストスイッチがトリガーされます。マルチプロセッサシステムでは、スレッドは代わりにスピンロックでミューテックスをポーリングする場合があります。これらの動作はどちらもパフォーマンスを低下させ、対称型マルチプロセッシング(SMP)システムのプロセッサ間でメモリバスの競合を引き起こす可能性があります。特にロックの 粒度が細かすぎる場合はその傾向が顕著です。
その他の同期 API には、条件変数、クリティカル セクション、セマフォ、モニターなどがあります。
スレッドを扱う一般的なプログラミングパターンとして、スレッドプールがあります。これは、起動時に一定数のスレッドを作成し、タスクの割り当てを待機するものです。新しいタスクが到着すると、スレッドプールは起動し、タスクを完了してから待機状態に戻ります。これにより、実行されるタスクごとに比較的コストのかかるスレッドの作成と削除の処理を回避でき、スレッド管理をアプリケーション開発者の手から解放し、スレッド管理の最適化に適したライブラリやオペレーティングシステムに委ねることができます。
マルチスレッド アプリケーションには、シングルスレッド アプリケーションに比べて次のような利点があります。
マルチスレッド アプリケーションには次の欠点があります。
多くのプログラミング言語は、何らかの形でスレッドをサポートしています。
{{cite AV media}}: CS1 maint: bot: 元のURLステータス不明(リンク)