1 はじめに
スレッドは次の 2 つの部分で構成されます。
- オペレーティング システムがスレッドを管理するために使用するスレッド カーネル オブジェクト。システムは、カーネル オブジェクトを使用してスレッド統計を保存することもできます。
- スレッド スタックは、スレッドの実行に必要なすべての関数パラメータとローカル変数を維持するために使用されます。
プロセスは怠惰です。何も実行することはなく、単なるスレッドのコンテナーです。スレッドはプロセスのコンテキストで作成する必要があり、プロセス内で「存続」します。これは、スレッドがプロセスのアドレス空間内でコードを実行し、データを処理することを意味します。プロセス コンテキストで 3 つ以上のスレッドが実行されている場合、これらのスレッドは同じアドレス空間を共有します。これらのスレッドは同じコードを実行し、同じデータを処理できます。ハンドルはスレッド固有ではなくプロセスごとであるため、これらのスレッドはカーネル オブジェクト ハンドルも共有します。
2. スレッドを作成する
CreateThreadが呼び出されると、システムはスレッド カーネル オブジェクトを作成します。このスレッド カーネル オブジェクトはスレッドそのものではなく、オペレーティング システムがスレッドを管理するために使用する小さなデータ構造です。スレッド カーネル オブジェクトは、スレッド統計で構成される小さなデータ構造であると考えてください。これは、プロセスとプロセス カーネル オブジェクト間の関係と同じです。
HANDLE CreateThread(
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] SIZE_T dwStackSize,
[in] LPTHREAD_START_ROUTINE lpStartAddress,
[in, optional] __drv_aliasesMem LPVOID lpParameter,
[in] DWORD dwCreationFlags,
[out, optional] LPDWORD lpThreadId
);
- lpThreadAttributes: 返されたハンドルを子プロセスが継承できるかどうかを決定する SECURITY_ATTRIBUTES 構造体へのポインタ。
- dwStackSize: スタックの初期サイズ (バイト単位)。このパラメータがゼロの場合、新しいスレッドはデフォルトのサイズを使用します。
- lpStartAddress: スレッドによって実行されるアプリケーション定義関数へのポインタ。
- lpParameter: スレッドに渡される変数へのポインタ。
- dwCreationFlags: スレッドの作成を制御するフラグ。
- lpThreadId: スレッド識別子を受け取る変数へのポインタ。このパラメータが NULL の場合、スレッド識別子は返されません。
例: スレッド内で加算を実行します。
DWORD WINAPI threadFun(PVOID pvParm)
{
int i = 0;
int x = *((int*)pvParm);
i += x;
printf("i = %d\n", i);
return 0;
}
DWORD threadID;
int x = 2;
HANDLE hThread = CreateThread(NULL, 0, threadFun, (PVOID)&x, 0, &threadID);
3. 実行中のスレッドを終了します
終了するには4つの方法があります。
- スレッド関数の戻り値 (強く推奨)
- スレッドは ExitThread 関数を呼び出して自身を終了します (非推奨)
- 別のプロセスのスレッドが TerminateThread 関数を呼び出します (非推奨)
- スレッドを含むプロセスが終了しました (非推奨)
3.1 スレッド関数の戻り値
スレッド関数を返すことで、次のアプリケーションの適切なクリーンアップが確実に実行されます。
- スレッド関数で作成されたすべての C++ オブジェクトは、デストラクターを介して適切に破棄されます。
- オペレーティング システムは、スレッド スタックによって使用されているメモリを適切に解放します。
- オペレーティング システムは、スレッドの関数の戻り値としてスレッドの終了コードを設定します。
- システムは、スレッドのカーネル オブジェクトの使用数をデクリメントします。
3.2ExitThread関数
スレッドを強制的に終了するには、ExitThread を呼び出します。
この関数はスレッドの実行を終了し、オペレーティング システムにスレッドが使用するすべてのオペレーティング システム リソースをクリーンアップさせます。ただし、C/C++ リソースは破棄されません。ExitProcess 関数と同じです。
3.3 TerminateThread関数
呼び出しスレッドを常に強制終了するExitThreadとは異なり、 TerminateThread は任意のスレッドを強制終了できます。
3.4 スレッドを含むプロセスが終了する
ExitProcess と TerminateProcess を使用して、スレッドの実行を終了できます。これらの関数により、終了したプロセス内のすべてのスレッドが終了します。同時に、プロセス全体がシャットダウンされるため、プロセスが使用するすべてのリソースは確実にクリーンアップされます。スレッドのスタックが含まれます。ただし、C++ オブジェクト デストラクターは呼び出されません。
4._beginthreadex _
C/C++ コードを作成している場合は、CreateThread を決して呼び出さないでください。代わりに _beginthreadex を使用し、終了には _endthreadex を使用する必要があります。
理由:まず、標準 C ランタイム ライブラリとマルチスレッドの間の矛盾から始める必要があります。標準 C ランタイム ライブラリは 1970 年に実装されました。当時はマルチスレッドをサポートするオペレーティング システムがなかったためです。したがって、標準 C ランタイム ライブラリを作成するプログラマは、マルチスレッド プログラムによる標準 C ランタイム ライブラリの使用を考慮していません。
たとえば、標準 C ランタイム ライブラリのグローバル変数 errno では、エラーが発生すると、ランタイム ライブラリ内の多くの関数がこのグローバル変数にエラー コードを割り当てます。
if (system("notepad.exe readme.txt") == -1)
{
switch(errno)
{
...//错误处理代码
}
}
あるスレッド A が上記のコードを実行しているとすると、そのスレッドが system() を呼び出した後、switch() ステートメントが呼び出される前に別のスレッド B が開始され、このスレッド B は標準 C ランタイム ライブラリの関数も呼び出します。この関数の実行中にエラーが発生し、エラー コードがグローバル変数 errno に書き込まれました。このようにして、スレッド A が switch ( ) ステートメントの実行を開始すると、スレッド B によって変更された errno にアクセスします。この状況は避けなければなりません! この変数だけが問題を引き起こすわけではないため、strerror()、strtok()、tmpnam()、gmtime()、asctime() などの他の関数でも、複数のスレッドのアクセス変更の問題によって引き起こされるデータ カバレッジが発生する可能性があります。
この問題を解決するために、Windows オペレーティング システムは、データ構造が作成され、C/C++ ランタイム ライブラリ関数を使用する各スレッドに関連付けられるという解決策を提供します。次に、C/C++ ランタイム ライブラリ関数を呼び出すとき、それらの関数は、他のスレッドへの影響を避けるために、呼び出しスレッドのデータ ブロックを見つけることを認識している必要があります。そして、このメモリ領域の作成は、C/C++ ランタイム ライブラリ関数 _beginthreadex() が担当します。
_beginthreadex は内部で CreateThread を呼び出します。
5. スレッドの一時停止と回復
スレッド カーネル オブジェクトには、スレッドのサスペンド数を表す値があります。CreateThreadが呼び出されると、システムはスレッド カーネル オブジェクトを作成し、一時停止カウントを 1 に初期化します。このようにして、CPU はこのスレッドに対してスケジュールされなくなります。
CreateThread 関数が呼び出されたときに、CREATE_SUSPENDED フラグが渡されるかどうかを確認します。そうである場合、関数は新しいスレッドを一時停止して戻ります。そうでない場合、関数はスレッドのサスペンドカウントを 0 にデクリメントします。スレッドは、イベントの発生 (キーボード入力など) をまだ待機していない限り、サスペンド カウントが 0 のときにスケジュール可能であると言われます。
再開するにはResumeThread を呼び出します。
成功すると、ResumeThread はスレッドの以前の一時停止カウントを返し、それ以外の場合は 0xFFFFFFFF を返します。
スレッドは複数回一時停止できます。スレッドが 3 回一時停止された場合、システムが CPU を割り当てる資格を得るには、そのスレッドを 3 回再開する必要があります。スレッドの作成時に CREATE_SUSPENDED フラグを使用するだけでなく、SuspendThread を使用してスレッドを一時停止することもできます。
6. スレッドスリープ
スレッドは、一定期間スケジュールする必要がないことをシステムに伝え、Sleep を呼び出してそれを実装することもできます。
void Sleep(
[in] DWORD dwMilliseconds
);
この関数は、スレッド自体を dwMilli秒単位 (ミリ秒単位) の間一時停止します。
- 値 0 を指定すると、スレッドは残りのタイム スライスを実行可能な他のスレッドに譲ります。他に実行できるスレッドがない場合、関数はすぐに戻り、スレッドは実行を続けます。
- INFINITE 値は、このスレッドをスケジュールしてはならないことをシステムに伝えることを意味します。
1 はじめに
スレッドは次の 2 つの部分で構成されます。
- オペレーティング システムがスレッドを管理するために使用するスレッド カーネル オブジェクト。システムは、カーネル オブジェクトを使用してスレッド統計を保存することもできます。
- スレッド スタックは、スレッドの実行に必要なすべての関数パラメータとローカル変数を維持するために使用されます。
プロセスは怠惰です。何も実行することはなく、単なるスレッドのコンテナーです。スレッドはプロセスのコンテキストで作成する必要があり、プロセス内で「存続」します。これは、スレッドがプロセスのアドレス空間内でコードを実行し、データを処理することを意味します。プロセス コンテキストで 3 つ以上のスレッドが実行されている場合、これらのスレッドは同じアドレス空間を共有します。これらのスレッドは同じコードを実行し、同じデータを処理できます。ハンドルはスレッド固有ではなくプロセスごとであるため、これらのスレッドはカーネル オブジェクト ハンドルも共有します。
2. スレッドを作成する
CreateThreadが呼び出されると、システムはスレッド カーネル オブジェクトを作成します。このスレッド カーネル オブジェクトはスレッドそのものではなく、オペレーティング システムがスレッドを管理するために使用する小さなデータ構造です。スレッド カーネル オブジェクトは、スレッド統計で構成される小さなデータ構造であると考えてください。これは、プロセスとプロセス カーネル オブジェクト間の関係と同じです。