マルチスレッド Win32 プログラムの作成
更新 : 2007 年 11 月
複数のスレッドを持つプログラムを作成するときは、各スレッドの動作とプログラム リソースの使用を調節する必要があります。また、各スレッドに個別のスタックを用意する必要もあります。
スレッド間のリソースの共有
メモ : |
---|
この問題に関する MFC の視点による説明については、「マルチスレッド : プログラミングのヒント」および「マルチスレッド : 同期クラスの使い分け」を参照してください。 |
各スレッドは、専用のスタックと CPU レジスタの専用コピーを持っています。ファイル、静的データ、ヒープ メモリなどのリソースは、プロセス内のすべてのスレッドで共有します。これらの共通リソースを使うスレッドは、同期をとる必要があります。Win32 には、セマフォや、クリティカル セクション、イベント、ミューテックスなど、リソースの同期をとるためにさまざまな方法が用意されています。
複数のスレッドが静的データにアクセスするときには、リソースの競合に備える必要があります。たとえば、あるスレッドが表示する項目の xy 座標が入っている静的データ構造体を別のスレッドで更新するプログラムを考えてみます。更新スレッドが x 座標を変更した後 y 座標を変える前にプリエンプトされてしまった場合は、y 座標が更新される前に表示スレッドがスケジュールされることがあります。その結果、項目は間違った場所に表示されます。このような問題は、セマフォを使って構造体へのアクセスを制御することにより解決できます。
ミューテックス (mutex は mutual exclusion = 相互排他の先頭の文字を組み合わせた造語) は、お互いに非同期で実行しているスレッドやプロセス間で通信する 1 つの方法です。この通信は、複数のスレッドやプロセスの処理を調整する一般的な方法で、共有リソースをロックまたはロックを解除することにより共有リソースへのアクセスを制御します。この xy 座標の更新問題を解決するために、更新スレッドは、更新を実行する前にデータ構造体が使用中であることを示すミューテックスを設定します。両方の座標が更新されたら、ミューテックスを解除します。表示スレッドは、ミューテックスが解除されるのを待ってから画面を更新する必要があります。この場合プロセスはブロックされて、ミューテックスが解除されるまで先へ進むことができないので、ミューテックスを待つこのプロセスは、ミューテックスによりブロッキングされているということがあります。
「マルチスレッドの C サンプル プログラム」に示した Bounce.c プログラムでは、ScreenMutex というミューテックスを使用して画面の更新を調整しています。表示スレッドのいずれかの表示の準備ができるたびに、表示スレッドは ScreenMutex へのハンドルと定数 INFINITE を引数として WaitForSingleObject を呼び出します。定数 INFINITE を指定すると、WaitForSingleObject がミューテックスによってプロセスをブロックし、タイムアウトを起さないことになります。ScreenMutex が解除されると、待機している関数がミューテックスをセットして、ほかのスレッドが表示に干渉できないようにしてスレッドの実行を続けます。ScreenMutex が解除されない場合、スレッドはミューテックスが解除されるまでブロックされます。スレッドは、表示更新を完了すると ReleaseMutex を呼び出してミューテックスを解除します。
画面表示と静的データは、注意深く管理する必要がある 2 つのリソース例にすぎません。たとえば、プログラムには、同じファイルにアクセスする複数のスレッドが存在する場合があります。別のスレッドがファイル ポインタを移動した可能性があるので、それぞれのスレッドは、読み書きする前にファイル ポインタをリセットする必要があります。さらに、各スレッドは、ポインタに位置をセットしてからファイルにアクセスするまでの間にプリエンプトされないようにする必要があります。これらのスレッドは、WaitForSingleObject 呼び出しと ReleaseMutex 呼び出しでそれぞれのファイル アクセスを囲むことにより、セマフォを使ってファイルへのアクセスを調整する必要があります。この手法を説明するコード例を次に示します。
HANDLE hIOMutex= CreateMutex (NULL, FALSE, NULL);
WaitForSingleObject( hIOMutex, INFINITE );
fseek( fp, desired_position, 0L );
fwrite( data, sizeof( data ), 1, fp );
ReleaseMutex( hIOMutex);
スレッドのスタック
アプリケーションの既定のスタック領域はすべて、スレッド 1 と呼ばれる、最初に実行されるスレッドに必ず割り当てられます。したがって、2 番目以降のスレッドのスタックに割り当てるメモリの量を指定する必要があります。オペレーティング システムは、必要に応じて、スレッドに対してスタック領域をさらに割り当てますが、既定値は指定する必要があります。
_beginthread 呼び出しの最初の引数は、スレッドを実行する BounceProc 関数へのポインタです。2 番目の引数では、スレッドに対する既定のスタック サイズを指定します。最後の引数は、BounceProc へ渡される ID 番号です。BounceProc は、ID 番号を使って乱数ジェネレータを起動して、スレッドのパレット番号と表示文字を選択します。
C のランタイム ライブラリまたは Win32 API を呼び出すスレッドは、呼び出すライブラリと API 関数のために十分なスタック領域を用意する必要があります。C 言語ライブラリの printf 関数には、500 バイト以上のスタック領域が必要です。また Win32 API ルーチンを呼び出すには、スタック領域として 2K バイト用意する必要があります。
スレッドはそれぞれ自分自身のスタックを持っているので、できる限り静的データを使わないことで、データ項目に対して起こりうる衝突を避けることができます。スレッドが持つ各データに対しては、すべて auto 自動スタック変数を使うようにプログラムをデザインします。Bounce.c プログラムのグローバル変数は、ミューテックスか、初期化後に変化しない変数のいずれかです。
Win32 には、スレッドの個別データを格納するために、スレッド ローカル ストレージ (TLS: Thread-Local Storage) も用意されています。詳細については、「スレッド ローカル ストレージ (TLS: Thread Local Storage)」を参照してください。