
「スレッド:はじめに」セクションを見てみましょう。 最初に、スレッドがどのように機能するかの簡単な概要を見てから、スレッドがどのように作成および完了されるかに焦点を当てます。 最後に、アプリケーション設計の2つの異なるアプローチ(マルチスレッドとマルチプロセス)を選択する際に考慮すべきいくつかの要因を検討します。
29.1。 短いレビュー
プロセスと同様に、スレッドは、単一のアプリケーション内で複数の並列タスクを同時に実行するためのメカニズムです。 図に示すように。 29.1では、1つのプロセスに複数のスレッドが含まれる場合があります。 それらはすべて、互いに独立して同じプログラム内で実行され、初期化/未初期化データとヒープセグメントを含むグローバルメモリを共有します(従来のUNIXプロセスはマルチスレッドプロセスの特殊なケースであり、1つのスレッドで構成されます)。
図 29.1いくつかの単純化が行われます。 特に、各スレッドのスタックの場所は、共有ライブラリおよび共有メモリ領域と交差する場合があります。 スレッドが作成され、ライブラリがロードされ、メモリの共通セクションがアタッチされた順序に依存します。 さらに、スレッドのスタックの場所は、Linuxディストリビューションによって異なる場合があります。
プロセス内のスレッドは同時に実行できます。 マルチプロセッサシステムでは、スレッドを並行して実行できます。 一方のスレッドがI / Oによりブロックされた場合、もう一方のスレッドが引き続き動作する場合があります(I / O専用の別のスレッドを作成する方が理にかなっていますが、代替のI / Oモデルの方が適していることがよくあります。詳細については、第59章を参照してください) 。
状況によっては、スレッドがプロセスより優先されます。 複数のプロセスを作成することにより、競争力のある実行に対する従来のUNIXアプローチを検討してください。 親プロセスが着信接続を受け入れ、各クライアントと通信する各fork()呼び出しで個別の子プロセスを作成するネットワークサーバーモデルの例を見てみましょう(セクション56.3を参照)。 これにより、複数の接続を同時に提供できます。 通常、このアプローチはうまく機能しますが、状況によっては次の制限につながります。

- プロセス間での情報交換には独自の問題があります。 親と子孫は(読み取り専用のテキストセグメントに加えて)メモリを共有しないため、データ交換には何らかの形式のプロセス間通信を使用する必要があります。
- fork()を使用してプロセスを作成すると、比較的多くのリソースが消費されます。 サブセクション24.2.2で説明したコピーアンドライト方式を使用しても、メモリページやファイル記述子を持つテーブルなど、さまざまなプロセス属性を複製する必要があります。 これは、fork()の呼び出しにまだかなりの時間がかかることを意味します。 スレッドは、これらの問題の両方を取り除くのに役立ちます。
- ストリーム間の情報交換は簡単で高速です。 これを行うには、データを共有変数(グローバルまたはヒープ)にコピーするだけです。 ただし、複数のスレッドが同時に同じ情報を更新しようとする状況で発生する可能性がある問題を回避するには、第30章で説明されている同期方法を適用する必要があります。
- スレッドの作成はプロセスの作成よりも時間がかかりません-通常、少なくとも10倍のパフォーマンスの向上があります(Linuxでは、スレッドはclone()システムコールを使用して実装されます。スレッドとfork()コールの速度の違いを表28.3に示します)。 fork()の場合のように、直接コピーするのではなく、多くの属性が単純に共有されるため、スレッド作成手順は高速です。 特に、メモリのページ(コピーオンライトを使用)とページのあるテーブルを複製する必要はありません。
グローバルメモリに加えて、スレッドは他の多くの属性も共有します(これは、個々のスレッドではなく、プロセス全体で属性がグローバルである場合です)。 その中には、以下にリストされている属性があります。
-プロセスとその親の識別子。
-プロセスグループとセッションの識別子。
-管理端末。
-プロセス資格情報(ユーザーおよびグループ識別子)。
-開いているファイルを処理します。
-fcntl()を呼び出して作成されたロックを記録します。
-信号のアクション。
-ファイルシステムに関連する情報:umask、現在およびルートディレクトリ。
-インターバルタイマー(setitimer())およびPOSIXタイマー(timer_create())。
-System Vのセマフォ(semadj)の中止値。
-リソースの制限。
-消費されたプロセッサー時間(times()から取得)。
-消費リソース(getrusage()から取得)。
-値nice(setpriority()およびnice()を使用して設定)。
以下は、個々のスレッドに固有の属性です。
-フロー識別子(セクション29.5を参照)。
-信号マスク。
-特定のストリームに関連するデータ(セクション31.3を参照)。
-代替シグナルスタック(sigaltstack())。
-変数errno。
-浮動小数点設定(env(3)を参照)。
-リアルタイムの計画ポリシーと優先度(セクション35.2および35.3を参照)。
-CPUバインディング(Linuxにのみ適用、セクション35.4で説明)。
-機能(Linuxにのみ適用、39章で説明)。
-スタック(ローカル変数と関数呼び出しのレイアウト情報)。
図に見られるように 29.1、個々のスレッドに関連するすべてのスタックは同じ仮想アドレス空間内にあります。 これは、適切なポインターを持つスレッドが、相互のスタックを介してデータを交換できることを意味します。 これは便利な場合もありますが、ローカル変数が置かれているスタックの有効期間中のみ有効であるという事実から生じる依存関係を解決するコードを記述する際には注意が必要です(関数が値を返す場合、そのスタックで使用されるメモリの一部は次の関数呼び出し中に再利用されます;スレッドが終了すると、そのスタックが正式に配置されていたメモリ部分が別のスレッドで利用可能になります)。 この依存関係の不適切な処理は、追跡が困難なエラーにつながる可能性があります。
29.2。 Pthreadsプログラミングインターフェイスの概要
1980年代後半から1990年代初頭には、ストリームを操作するためのプログラミングインターフェイスがいくつかありました。 1995年に、POSIX.1 APIはPOSIXスレッドAPIを記述しました。これは後にSUSv3の一部になりました。 Pthreadsプログラミングインターフェイスは、いくつかの概念に基づいています。 実装について詳しく調べながら、それらについて詳しく説明します。
Pthreadsのデータ型
Pthreadsプログラミングインターフェイスは、いくつかのデータ型を定義します。その一部を表にリストします。 29.1。 それらのほとんどについては、次のページで説明します。

SUSv3標準には、これらのデータ型の表現方法に関する詳細が含まれていないため、ポータブルアプリケーションでは、これらのデータ型を不透明と見なす必要があります。 これは、プログラムがこれらのタイプの変数の構造または内容に依存してはならないことを意味します。 特に、==演算子を使用してこのような変数を比較することはできません。
ストリームとerrno変数
従来のUNIXプログラミングインターフェイスでは、errno変数はグローバルで整数です。 ただし、これはマルチスレッドプログラムには十分ではありません。 スレッドがerrnoグローバル変数にエラーを書き込んだ関数を呼び出した場合、これは関数を呼び出してerrnoの値をチェックする他のスレッドに誤解を招く可能性があります。 つまり、結果は競合状態になります。 したがって、マルチスレッドプログラムでは、各スレッドにerrnoの独自のインスタンスがあります。 Linux(およびほとんどのUNIX実装)では、これにおよそ1つのアプローチが使用されます:errnoはマクロとして宣言され、個々のスレッドに固有のlvalue形式の可変値を返す関数呼び出しに展開されます(lvalueの値は変更のためにアクセスできるため、前と同様に、マルチスレッドプログラムにerrno = valueの形式の割り当て操作を記述できます。
上記の要約:エラー報告手順がUNIXプログラミングインターフェイスで使用される従来のアプローチと完全に一致するように、errnoメカニズムがスレッドに統合されました。
Pthreadsの関数によって返される値
従来、システムコールと一部の関数は、成功すると0を返し、エラーが発生すると–1を返します。 エラー自体を示すために、errno変数が使用されます。 Pthreadsプログラミングインターフェイスの関数の動作は異なります。 成功した場合は0も返されますが、エラーがある場合は正の値が使用されます。 これは、従来のUNIXシステムコールでerrnoに割り当てることができる値の1つです。
マルチスレッドプログラムのerrnoへの各リンクは、関数の呼び出しに関連するオーバーヘッドを運ぶため、プログラムは、Pthreadsから関数によって返される値をこの変数に直接割り当てません。 代わりに、以下に示すように、中間変数と診断関数errExitEN()(サブセクション3.5.2を参照)を使用します。
pthread_t *thread; int s; s = pthread_create(&thread, NULL, func, &arg); if (s != 0) errExitEN(s, "pthread_create");
Pthreadsのコンパイル
Linuxでは、Pthreadsプログラミングインターフェイスを使用するプログラムは、cc -pthreadパラメーターを使用してコンパイルする必要があります。 このパラメーターのアクションの中で、次のものを区別できます。
- _REENTRANTプリプロセッサマクロが宣言されています。 これにより、いくつかの再入可能(再入可能)関数の宣言にアクセスできます。
- プログラムは、libpthreadライブラリとリンクされます(-lpthreadパラメーターと同等)。
マルチスレッドプログラムの特定のコンパイルオプションは、実装(およびコンパイラ)によって異なります。 一部のシステム(Tru64など)もcc-pthreadを使用しますが、cc -mtはSolarisおよびHP-UXで使用されます。
29.3。 スレッド作成
プログラムを開始した直後の最終プロセスは、ソースまたはメインと呼ばれる1つのスレッドで構成されます。 このセクションでは、追加のスレッドを作成する方法を学習します。
pthread_create()関数は、新しいスレッドを作成するために使用されます。

新しいスレッドは、開始値として指定された関数を呼び出して引数引数(つまり、start(arg))を受け入れることで実行を開始します。 pthread_create()を呼び出したスレッドは、この呼び出しに続くステートメントを実行することにより実行を継続します(これは、セクション28.2で説明した、glibcライブラリからのclone()システムコールのラッパー関数の動作に対応します)。
arg引数はvoid *として宣言されます。 これは、任意のタイプのオブジェクトへのポインターを開始関数に渡すことができることを意味します。 通常、グローバルスペースまたはヒープにある変数を指しますが、NULL値を使用することもできます。 start関数にいくつかの引数を渡す必要がある場合、これらの引数を個別のフィールドとして含む構造体へのポインタをargとして提供できます。 適切なキャストを使用して、argを整数(int)として指定することもできます。
厳密に言えば、C言語の標準では、intをvoid *にキャストした結果は記述されていません。 ただし、ほとんどのコンパイラはこの操作を許可し、予測可能な結果、つまりint j ==(int)((void *)j)を生成します。
start関数によって返される値もvoid *型であり、arg引数として解釈できます。 以下では、pthread_join()関数を見ると、この値がどのように使用されるかがわかります。
初期ストリーム関数によって返された値を整数にキャストするときは注意が必要です。 実際、ストリームがキャンセルされたときに返されるPTHREAD_CANCELED値(第32章を参照)は、通常、void *への整数キャストとして実装されます。 初期関数がこの値を返す場合、pthread_join()を実行している別のスレッドは、これをスレッドキャンセル通知として誤って受け入れます。 スレッドのキャンセルを許可し、初期関数から返される値として整数を使用するアプリケーションでは、スレッドが正常に終了するときに、これらの値がPTHREAD_CANCELED定数と一致しないことを確認する必要があります(現在の実装でPthreads)。 ポータブルアプリケーションも同じように動作しますが、動作可能なすべての実装が必要です。
スレッド引数は、pthread_t型のバッファを指し、pthread_create()は、関数を返す前に、作成されたスレッドの一意の識別子を書き込みます。 この識別子は、Pthreadへのさらなる呼び出しでこのスレッドを参照するために使用できます。
SUSv3標準では、スレッドが指すバッファは、新しいスレッドを開始する前に初期化する必要がないと明確に規定されています。 つまり、pthread_create()関数が戻る前に、新しいスレッドが動作を開始できます。 新しいスレッドが独自の識別子を取得する必要がある場合、このためにpthread_self()関数(セクション29.5で説明)を使用する必要があります。
attr引数は、新しいスレッドのさまざまな属性を含むpthread_attr_tオブジェクトへのポインターです(セクション29.8に戻ります)。 attrをNULLに設定すると、デフォルトの属性でストリームが作成されます。これは、本書のほとんどの例で行うことです。
プログラムは、pthread_create()を呼び出した後、スケジューラーがプロセッサー時間を割り当てるスレッドを認識しません(マルチプロセッサーシステムでは、両方のスレッドを異なるCPUで同時に実行できます)。 特定の計画手順に明示的に依存するプログラムは、24.4項で説明したのと同じ種類の競合状態の影響を受けます。 特定の実行順序を保証する必要がある場合は、第30章で説明した同期方法のいずれかを使用する必要があります。
29.4。 スレッド補完
スレッドの実行は、次のいずれかの理由で終了します。
- 初期関数はreturnステートメントを実行し、ストリームの戻り値を示します。
- スレッドはpthread_exit()関数を呼び出します(以下で説明します)。
- スレッドは、pthread_cancel()関数(セクション32.1を参照)を使用してキャンセルされます。
- スレッドのいずれかがexit()を呼び出すか、メインスレッドがreturnステートメント(main()関数内)を実行します。これにより、プロセス内のすべてのスレッドが即座に終了します。
pthread_exit()関数は、呼び出しスレッドを終了し、pthread_join()関数を使用して別のスレッドから取得できる戻り値を示します。
include <pthread.h> void pthread_exit(void *retval);
pthread_exit()の呼び出しは、スレッドの初期関数内でreturnステートメントを実行することと同等です。ただし、pthread_exit()は、初期関数によって起動されたコードから呼び出すことができます。
retval引数には、ストリームによって返される値が保持されます。 retvalが指す値は、pthread_exit()の呼び出しの終了時にその内容が未定義になるため、スレッド自体のスタック上にあるべきではありません(プロセスの仮想メモリのこの部分は、新しいスレッドのスタックにすぐに割り当てられます)。 初期関数のreturnステートメントで渡される値についても同じことが言えます。
メインスレッドがexit()またはreturnステートメントの代わりにpthread_exit()を呼び出した場合、残りのスレッドは引き続き実行されます。
29.5。 ストリーム識別子
プロセス内の各スレッドには、独自の一意の識別子があります。 pthread_create()によって呼び出しスレッドに返されます。 さらに、pthread_self()関数を使用して、スレッドは独自の識別子を取得できます。
include <pthread.h> pthread_t pthread_self(void);
呼び出しスレッドの識別子を返します
アプリケーション内のフロー識別子は、次のように使用できます。
- Pthreadsのさまざまな機能に関与して、実行中のスレッドを判別します。 例には、関数pthread_join()、pthread_detach()、pthread_cancel()、およびpthread_kill()が含まれます。 これらはすべて、この章および後続の章で説明されています。
- 一部のアプリケーションでは、特定のストリームの識別子を使用して動的データ構造にラベルを付けることが理にかなっている場合があります。 したがって、構造の作成者と「所有者」を決定できます。 また、データ構造を使用して後続のアクションを実行するスレッドを決定することもできます。
pthread_equal()関数を使用すると、2つのスレッド識別子のIDを確認できます。

たとえば、呼び出しスレッドの識別子がtid変数に格納されている値と一致するかどうかを確認するには、次のコードを記述できます。
if (pthread_equal(tid, pthread_self()) printf("tid matches self\n");
pthread_tデータ型は不透明として認識される必要があるため、pthread_equal()関数の必要性が生じます。 Linuxでは、unsigned long型ですが、他のシステムでは、ポインターまたは構造体にすることができます。
NPTLライブラリでは、pthread_tは実際にはunsigned longにキャストされるポインターです。
SUSv3標準では、pthread_t型をスカラーにする必要はありません。 構造物である可能性があります。 したがって、上記のストリーム識別子を出力するためのコードは移植性がありません(ただし、Linuxを含む多くのシステムで機能し、デバッグ中に役立ちます)。
pthread_t thr; printf("Thread ID = %ld\n", (long) thr); /* ! */
Linuxでは、スレッド識別子はすべてのプロセスに固有です。 ただし、他のシステムではそうではない場合があります。 SUSv3標準では、特に、ポータブルアプリケーションはこれらの識別子に依存して他のプロセスのスレッドを識別することはできないと規定しています。 また、pthread_join()関数を使用して完了したスレッドを接続した後、または切断されたスレッドを完了した後、ストリームライブラリがこれらの識別子を再利用できることを示します(pthread_join()関数については次のセクションで、切断されたスレッドはセクション29.7で説明します)。
gettid()システムコール(Linuxでのみ利用可能)によって返されるPOSIXと通常のストリームの識別子は同じではありません。 POSIXスレッド識別子は、スレッドライブラリの実装によって割り当てられ、維持されます。 通常のスレッド識別子はgettid()を呼び出すことで返され、カーネルによって割り当てられた番号(プロセス識別子に類似)です。 NPTLライブラリはカーネルによって発行された一意のスレッド識別子を使用しますが、アプリケーションはそれらについて知る必要がないことがよくあります(さらに、それらを操作すると、異なるシステム間の移植性がアプリケーションから奪われます)。
29.6。 完了したスレッドに参加する
pthread_join()関数は、thread引数で指定されたスレッドが終了するまで待機します(スレッドが既に完了している場合は、すぐに戻ります)。 この操作は参加と呼ばれます。

retval引数がゼロ以外のポインターである場合、関数は、完了したスレッドの戻り値のコピー、つまり、スレッドがreturnステートメントを実行したとき、またはpthread_exit()を呼び出して指定された値のコピーを受け取ります。
すでに接続されているスレッドの識別子に対してpthread_join()関数を呼び出すと、予期しない結果が生じる可能性があります。 たとえば、後で作成されたスレッドに参加して、同じ識別子を再利用できます。
スレッドが切断されていない場合(セクション29.7を参照)、pthread_join()関数を使用してスレッドを結合する必要があります。 これに失敗すると、完成したストリームは「ゾンビ」プロセスに類似したものになります(セクション26.2を参照)。 リソースの浪費に加えて、これは新しいスレッドを作成できなくなるという事実につながる可能性があります(十分な「ゾンビ」フローが蓄積した場合)。
pthread_join()がスレッド上で実行する手順は、プロセスのコンテキストでのwaitpid()呼び出しに似ています。 しかし、それらの間には顕著な違いがあります。
retval引数がゼロ以外のポインターである場合、関数は、完了したスレッドの戻り値のコピー、つまり、スレッドがreturnステートメントを実行したとき、またはpthread_exit()を呼び出して指定された値のコピーを受け取ります。
すでに接続されているスレッドの識別子に対してpthread_join()関数を呼び出すと、予期しない結果が生じる可能性があります。 たとえば、後で作成されたスレッドに参加して、同じ識別子を再利用できます。
スレッドが切断されていない場合(セクション29.7を参照)、pthread_join()関数を使用してスレッドを結合する必要があります。 これに失敗すると、完成したストリームは「ゾンビ」プロセスに類似したものになります(セクション26.2を参照)。 リソースの浪費に加えて、これは新しいスレッドを作成できなくなるという事実につながる可能性があります(十分な「ゾンビ」フローが蓄積した場合)。
pthread_join()がスレッド上で実行する手順は、プロセスのコンテキストでのwaitpid()呼び出しに似ています。 しかし、それらの間には顕著な違いがあります。
- ストリームには階層がありません。 プロセス内のどのスレッドもpthread_join()関数を使用して、同じプロセス内の別のスレッドに参加できます。 たとえば、スレッドAがスレッドBを作成し、後でスレッドBを作成したとします。 この場合、ストリームAはストリームBに参加でき、逆も同様です。 これは、プロセス間の階層関係とは異なります。 親プロセスがfork()を使用して子を作成した場合、彼と彼だけがwait()呼び出しを使用してこの子を期待できます。 pthread_create()関数を呼び出すスレッドと作成されるスレッドの間には、このような関係はありません。
- 「任意のスレッド」に参加することは不可能です(プロセスの場合、waitpid(–1、&status、options)を呼び出すことでこれを行うことができます)。 また、非ブロッキング結合操作もありません(WNOHANGフラグを指定してwaitpid()を呼び出すのと同様)。 同様の結果は、条件変数を使用して実現できます。 この例は、サブセクション30.2.4に示されています。
pthread_join()関数が特定の識別子を持つスレッドのみを接続できるという制限は、意図的に作成されました。 アイデアは、プログラムが知っているスレッドのみに参加するというものです。 「任意のストリームに結合する」操作の問題は、ストリームに階層がないという事実から発生するため、この方法で、ライブラリ関数(セクション30.2で説明した条件変数を使用して作成された.4、既知のストリームのみを結合できます)。 その結果、ライブラリはそのステータスを取得するためにこのストリームに参加できなくなり、すでに接続されているストリームに参加しようとするとエラーが発生します。 言い換えると、「任意のストリームへの結合」操作は、アプリケーションのモジュール式アーキテクチャと互換性がありません。
プログラム例
リスト29.1に示すプログラムは、新しいスレッドを作成して結合します。
リスト29.1 Pthreadsライブラリを使用した簡単なプログラム
______________________________________________________________threads/simple_thread c #include <pthread.h> #include "tlpi_hdr.h" static void * threadFunc(void *arg) { char *s = (char *) arg; printf("%s", s); return (void *) strlen(s); } int main(int argc, char *argv[]) { pthread_t t1; void *res; int s; s = pthread_create(&t1, NULL, threadFunc, "Hello world\n"); if (s != 0) errExitEN(s, "pthread_create"); printf("Message from main()\n"); s = pthread_join(t1, &res); if (s != 0) errExitEN(s, "pthread_join"); printf("Thread returned %ld\n", (long) res); exit(EXIT_SUCCESS); } ______________________________________________________________threads/simple_thread.c
このプログラムを実行すると、次のように表示されます。
$ ./simple_thread Message from main() Hello world Thread returned 12
最初の2行の出力順序は、スケジューラが2つのスレッドを管理する方法によって異なります。
29.7。 ストリームを切断する
デフォルトでは、ストリームは結合可能です。 これは、完了すると、pthread_join()関数を使用して別のスレッドからステータスを取得できることを意味します。 ストリームによって返されるステータスは重要ではない場合があります。 システムがリソースを自動的に解放し、スレッドが終了したらスレッドを削除する必要があります。 この場合、pthread_detach()関数を使用してスレッド引数にスレッドIDを指定することにより、スレッドを切断済みとしてマークできます。

pthread_detach()関数の使用例は、スレッドがそれ自体を切断する次の呼び出しです。
pthread_detach(pthread_self());
スレッドが既に切断されている場合、pthread_join()関数を使用して戻りステータスを取得することはできません。 また、再びドッキング可能にすることもできません。
切断されたスレッドは、別のスレッドで行われたexit()呼び出しや、メインプログラムで実行されたreturnステートメントに対して耐性を持ちません。 これらの状況のいずれにおいても、接続されているかどうかに関係なく、プロセス内のすべてのスレッドはすぐに終了します。 言い換えると、pthread_detach()関数は、スレッドが終了した後の動作に責任を負いますが、スレッドが終了した状況には責任を負いません。
29.8。 ストリーム属性
タイプpthread_attr_tのpthread_create()関数のattr引数を使用して、新しいスレッドを作成するときに使用される属性を指定できることは既に述べました。 これらの属性(詳細については、この章の最後にリストされているリンクを参照)の検討や、pthread_attr_tオブジェクトを操作できるPthreadのさまざまな関数のプロトタイプの調査は行いません。 これらの属性には、ストリームスタックの場所とサイズ、そのスケジューリングポリシーと優先度(セクション35.2と35.3で説明されているリアルタイムスケジューリングポリシーとプロセスの優先度に類似)などの情報、およびストリームが接続可能か切断済みか。
これらの属性の使用例をリスト29.2に示します。この例では、スレッドが作成され、そのスレッドはその出現時に切断されます(その後のpthread_detach()の呼び出しの結果としてではありません)。 最初に、このコードはデフォルト値を使用して属性で構造を初期化し、切断されたストリームを作成するために必要な属性を設定し、この構造を使用して新しいストリームを作成します。 作成手順の最後に、属性を持つオブジェクトは不要として削除されます。
リスト29.2。 切断属性を持つスレッドの作成
__________________________________________________ threads/detached_attrib.c #include <pthread.h> #include "tlpi_hdr.h" static void * threadFunc(void *x) { return x; } int main(int argc, char *argv[]) { pthread_t thr; pthread_attr_t attr; int s; s = pthread_attr_init(&attr); /* */ if (s != 0) errExitEN(s, "pthread_attr_init"); s = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (s != 0) errExitEN(s, "pthread_attr_setdetachstate"); s = pthread_create(&thr, &attr, threadFunc, (void *) 1); if (s != 0) errExitEN(s, "pthread_create"); s = pthread_attr_destroy(&attr); /* */ if (s != 0) errExitEN(s, "pthread_attr_destroy"); s = pthread_join(thr, NULL); if (s != 0) errExitEN(s, "pthread_join failed as expected"); exit(EXIT_SUCCESS); } __________________________________________________ threads/detached_attrib.c
29.9。 スレッドとプロセスの比較
このセクションでは、アプリケーションの基盤としてスレッドとプロセスを選択する際に考慮すべきいくつかの要因について簡単に説明します。 まず、マルチスレッドアプローチの利点について説明します。
- スレッド間でデータを交換するのは簡単です。 同じデータ交換ですが、プロセス間でより多くのコストが必要です(たとえば、共通メモリセグメントの作成やパイプラインの使用など)。
- スレッドの作成は、プロセスの作成よりも時間がかかりません。 スレッドは、コンテキストの切り替え速度の面でもメリットがあります。 ただし、スレッドにはプロセスと比較して特定の欠点があります。
- , , ( 31.1). .
- (, ) , . , .
- 各スレッドは、そのプロセスの最終アドレス空間を使用する能力を競います。特に、各スレッドのスタックとローカルストレージは、プロセスの仮想メモリの一部を消費するため、他のスレッドからアクセスできなくなります。また、アドレススペースは非常に大きい(たとえば、x86-32アーキテクチャの場合は3 GB)が、この要因は大きな制限になり、プロセスが大量のスレッドや大量のメモリを必要とするスレッドを作成するのを制限する可能性がある。一方、個別のプロセスは、RAMのサイズとスワップ領域のみによって制限される、空き仮想メモリの全範囲を使用できます。
以下は、スレッドとプロセスの選択に影響する可能性のある追加のポイントです。
- ( , ). 33.2.
- (, ). .
- , (, , , , () ). , , .
29.10. まとめ
マルチスレッドプロセスでは、同じプログラムで異なるスレッドが同時に実行されます。すべてに共通のグローバル変数と束がありますが、それぞれにローカル変数用の独自の個別のスタックがあります。同じプロセスの異なるスレッドも、プロセス識別子、開いているファイル記述子、シグナルアクション、現在のディレクトリ、リソース制限など、多くの属性を共有します。
スレッドの重要な機能は、プロセスと比較して情報の交換が簡単なことです。このため、一部のソフトウェアアーキテクチャは、マルチプロセスアプローチよりもマルチスレッドアプローチのほうが優れています。さらに、状況によっては、スレッドのパフォーマンスが向上する場合があります(たとえば、スレッドはプロセスよりも速く作成されます)が、この要素は通常、スレッドとプロセスを選択する場合に二次的です。
pthread_create(). , pthread_exit() ( exit() , ). (, pthread_detach()), pthread_join(), .
»
»
»
20% — Linux