レガシー Windows API(概要)について

ここで紹介しているAPIは、Windows95、Windows NT3.5時代のAPI群であり、最新のAPIは含まれていないことに注しして欲しい。なお、当時の解説書は現在よりもより詳細に解説しているため、参考になるはずである。

  • レイアウトを変換する際、崩れているページがあります。
  • 概要およびSampleコードはありますが、APIの解説はありません。

ご要望があれば旧版のWin32 API(HELP形式)をお譲りします。問い合わせフォームより申し込みください。

Windows API 同期の概要

Microsoft(R) Win32(R) アプリケーション プログラミング インターフェイス (API) では、 複数のスレッドの実行を調和させるためのさまざまな方法を用意しています。このトピックでは、 次に示す同期機構の使い方を説明します。

待機関数と同期オブジェクト

オーバーラップ入出力 (I/O) 操作

クリティカル セクション オブジェクト

変数のインターロック アクセス

次に示すトピックでは、 同期について説明しています。

待機関数

同期オブジェクト

ミューテックス オブジェクト

セマフォ オブジェクト

イベント オブジェクト

ミューテックス、 セマフォ、 イベント オブジェクトの共有

同期入出力とオーバーラップ入出力

クリティカル セクション オブジェクト

インターロック変数アクセス

名前付きオブジェクトの使用

複数のオブジェクトの待機

イベント オブジェクトの使用

ミューテックス オブジェクトの使用

セマフォ オブジェクトの使用

クリティカル セクション オブジェクトの使用

同期関数

待機関数

Win32 APIでは、 スレッドが自分自身の実行をブロックするための「待機関数」のセットを用意しています。待機関数は、 パラメータで指定された条件が成立するまで戻りません。パラメータには、 タイム アウト時間や、 同期オブジェクトのハンドルなどがあります。同期オブジェクトについて詳しくは、 同期オブジェクトを参照してください。

同期関数は、 呼び出されると、 指定された同期オブジェクトの状態などの終了条件を調べます。この初期状態が指定された条件を満足しておらず、 タイム アウト時間が経過していなければ、 呼び出し側のスレッドは効率的な待機状態に入ります。この状態では、 プロセッサ時間をほとんど消費せずに、 条件が成立するのを待ちます。永久に待機するには、 タイム アウト時間にINFINITEを設定してください。

WaitForSingleObject関数とWaitForSingleObjectEx関数には、 同期オブジェクトのハンドルが必要です。これらの関数は、 オブジェクトが「シグナル状態」になるか、 タイム アウト時間が経過すると戻ります (同期オブジェクトの状態は、 「シグナル状態」または「非シグナル状態」のどちらかです)

スレッドは、 WaitForMultipleObjects関数、 WaitForMultipleObjectsEx関数、 MsgWaitForMultipleObjects関数に、 1つ以上の同期オブジェクト ハンドルを含む配列を指定できます。これらの関数は、 指定したオブジェクトのいずれかがシグナル状態になったときに戻るように設定できます。この場合、 関数は、 関数の終了の原因となったオブジェクトの配列インデックスを返します。また、 すべてのオブジェクトが同時にシグナル状態になったときだけ関数が戻るように設定することもできます。この場合、 関数は、 指定された条件が成立したかどうかを示す値を返すだけです。

これらの複数オブジェクト関数は、 すべてのオブジェクトがシグナル状態になるのを待つ場合、 すべてのオブジェクトが同時にシグナル状態になるまで、 指定されたオブジェクトの状態を修正しません。たとえば、 ミューテックスがシグナル状態になっても、 ほかのオブジェクトもシグナル状態になるまで、 呼び出し側スレッドはミューテックスの所有権を取得しません。その間にほかのスレッドがミューテックスの所有権を取得すると、 指定した条件がすべて成立するのが遅れます。

WaitForSingleObjectEx関数とWaitForMultipleObjectsEx関数は、 ほかの待機関数とは異なり、 「アラート可能待機」を実行することもできます。アラート可能待機では、 指定した条件が成立すると関数は戻りますが、 システムが待機スレッドの実行キューに完了ルーチンを置いたときにも関数は戻ります。アラート可能待機と入出力 (I/O) 完了ルーチンについて詳しくは、 同期入出力とオーバーラップ入出力を参照してください。

MsgWaitForMultipleObjects関数はWaitForMultipleObjectsと同じですが、 指定された種類の入力が呼び出し側スレッドの入力キューで利用可能になったときにも戻ります。たとえば、 スレッドは、 MsgWaitForMultipleObjectsを使って、 特定のオブジェクトがシグナル状態になるか、 スレッドの入力キューにマウス入力が置かれるまで実行をブロックできます。入力が利用可能になったら、 スレッドはGetMessage関数かPeekMessage関数を使って入力を取得できます。

待機関数は、 戻る前に、 同期オブジェクトの状態を変更する場合があります。状態が修正されるのは、 シグナル状態になると関数が終了するオブジェクトの場合だけです。待機関数は、 同期オブジェクトの状態を次のように修正します。

セマフォ オブジェクトの場合、 カウントを1つ減らします。カウントが0ならば、 セマフォを非シグナル状態に変更します。

ミューテックス、 自動リセット イベント、 変更通知オブジェクトの場合は、 非シグナル状態に変更します。

手動リセット イベント、 プロセス、 スレッド、 コンソール入力オブジェクトは、 待機関数の影響を受けません。

同期オブジェクト

「同期オブジェクト」とは、 複数のスレッドの実行を調和させる待機関数にハンドルを指定できるオブジェクトです。同期オブジェクトの状態は「シグナル状態」か「非シグナル状態」のいずれかです。オブジェクトが非シグナル状態の間は関数は戻らず、 シグナル状態になると関数は戻ります。1つの同期オブジェクトのハンドルを複数のプロセスが所有することによって、 プロセス間の同期を取ることができます。

Win32 APIでは、 同期専用として次の3種類のオブジェクトが提供されています。

種類 説明

イベントイベントが発生したことを1つ以上の待機スレッドに通知します。

ミューテックス一度に1つのスレッドしか所有できないため、 複数のスレッドが共有リソースに相互排他的にアクセスするのに使えます。

セマフォ0から一定の最大値までのカウント値をとります。共有リソースに同時にアクセスできるスレッドの個数を制限します。

次に示すオブジェクトはほかの用途で使われるものですが、 同期にも使えます。

オブジェクト 説明

変更通知FindFirstChangeNotification関数で作成します。指定したディレクトリやディレクトリ ツリーで指定した種類の変更が発生するとシグナル状態になります。詳しくは、 ファイルの概要を参照してください。

コンソール入力コンソールを作成するとき、 CONIN$を指定したCreateFile関数がコンソール入力のハンドルを返すとき、 GetStdHandle関数がコンソール入力のハンドルを返すときに作成されます。コンソールの入力バッファに未読入力があるときにシグナル状態になり、 入力バッファが空ならば非シグナル状態になります。コンソールについて詳しくは、 文字モード サポートの概要を参照してください。

プロセスCreateProcess関数を呼び出して新しいプロセスを作成したときに作成されます。プロセスが動作しているときは非シグナル状態で、 プロセスが終了するとシグナル状態になります。プロセスについて詳しくは、 プロセスとスレッドの概要を参照してください。

スレッドCreateProcess関数、 CreateThread関数、 CreateRemoteThread関数を呼び出して新しいスレッドを作成したときに作成されます。スレッドが動作しているときは非シグナル状態で、 スレッドが終了するとシグナル状態になります。スレッドについて詳しくは、 プロセスとスレッドの概要を参照してください。

また、 ファイルや名前付きパイプ、 通信デバイスを同期オブジェクトとして使える場合もあります。しかし、 そのような使い方はできるだけ避けてください。ファイル、 名前付きパイプ、 通信について詳しくは、 同期入出力とオーバーラップ入出力を参照してください。

ミューテックス オブジェクト

「ミューテックス オブジェクト」とは、 スレッドがオブジェクトを所有していないときはシグナル状態になり、 スレッドがオブジェクトを所有しているときは非シグナル状態になる同期オブジェクトです。ミューテックスを所有できるスレッドは一度に1つだけです。このオブジェクトの名前は、 共有リソースの相互排他 (MUTually EXclusive) アクセスに使われることに由来しています。たとえば、 2つのスレッドが共有メモリに同時に書き込まないようにするには、 各スレッドがミューテックスの所有権を取得してからメモリにアクセスするコードを実行します。共有メモリに書き込んだら、 ミューテックスを解放します。

ミューテックス オブジェクトを作成するには、 CreateMutex関数を使います。ミューテックスを作成するオブジェクトは、 ミューテックスの所有権をすぐに取得でき、 ミューテックス オブジェクトの名前も指定できます。ほかのプロセスのスレッドは、 ミューテックスの名前を指定してOpenMutex関数を呼び出すことによって、 既存のミューテックス オブジェクトのハンドルをオープンできます。ミューテックス オブジェクト、 イベント オブジェクト、 セマフォ オブジェクトの名前について詳しくは、 プロセス間の同期を参照してください。

ミューテックス オブジェクトのハンドルを持つスレッドは、 待機関数を使って、 ミューテックスの所有権を要求できます。ミューテックスをほかのスレッドが所有している場合、 待機関数は、 ミューテックスを所有するスレッドがReleaseMutex関数を使ってミューテックスを解放するまで、 ミューテックスを要求したスレッドの実行をブロックします。待機関数の戻り値は、 ミューテックスがシグナル状態に変化したために関数が戻ったかどうかを示します。

スレッドは、 ミューテックスの所有権をいったん取得したら、 実行をブロックせずに、 同じミューテックスを指定して待機関数を何度も呼び出すことができます。このため、 自分がすでに所有しているミューテックスを待機することによるデッドロックが発生することはありません。このような状況で所有権を解放するには、 ミューテックスが待機関数の条件を満たすたびに、 ReleaseMutexを呼び出してください。

スレッドがミューテックス オブジェクトの所有権を解放せずに終了すると、 そのミューテックスは放棄されたものとみなされます。待機中のスレッドは放棄されたミューテックスの所有権を獲得できますが、 待機関数の戻り値は、 ミューテックスが放棄されていることを示します。ミューテックスが放棄されている場合、 エラーが発生し、 ミューテックスが保護している共有リソースは未定義の状態になっていると考えてください。ミューテックスが放棄されていないものとみなしてスレッドが通常どおり処理を行うと、 そのスレッドが所有権を解放したときにミューテックスの放棄フラグはクリアされます。これによって、 それ以降このミューテックスを待機関数に指定すると、 通常のミューテックスとして動作するようになります。

セマフォ オブジェクト

「セマフォ オブジェクト」とは、 0から一定の最大値までのカウントを管理する同期オブジェクトです。カウントが0よりも大きいときはセマフォはシグナル状態になり、 カウントが0ならば非シグナル状態になります。

セマフォ オブジェクトは、 一定数のユーザーだけをサポートする共有リソースをサポートするときに使います。セマフォは入退場ゲートのように動作し、 入場するスレッドと退場するスレッドを数えて、 スレッドを共有するスレッドの個数を一定の最大値に制限します。たとえば、 アプリケーションが作成するウィンドウの個数を制限するとします。ウィンドウの最大数と同じ最大カウントのセマフォを作成し、 ウィンドウを作成するときはカウントを1つ減らし、 クローズするときはカウントを1つ増やします。ウィンドウを作成する前に、 セマフォ オブジェクトを指定して待機関数を呼び出します。カウントが0になったときはウィンドウの作成制限に達したことを示し、 待機関数はウィンドウ作成コードの実行をブロックします。

セマフォ オブジェクトを作成するには、 CreateSemaphore関数を使います。この関数には、 オブジェクトの初期カウントと最大カウントを指定します。初期カウントは、 0より大きく、 最大カウント以下でなければなりません。また、 セマフォ オブジェクトの名前も指定できます。ほかのプロセスのスレッドは、 セマフォ オブジェクトの名前を指定してOpenSemaphore関数を呼び出すことによって、 既存のセマフォ オブジェクトのハンドルをオープンできます。ミューテックス オブジェクト、 イベント オブジェクト、 セマフォ オブジェクトの名前について詳しくは、 プロセス間の同期を参照してください。

セマフォがシグナル状態になったために待機関数が戻るとき、 セマフォのカウントは1つ減らされます。ReleaseSemaphore関数は、 セマフォのカウントを指定された数だけ減らします。カウントは0未満にはならず、 最大カウントより大きくなることもありません。

通常、 セマフォの初期カウントは、 最大値に設定します。その後、 保護するリソースの消費レベルに応じて、 カウントを減らします。また、 初期カウント0でセマフォを作成することによって、 アプリケーションが初期化を行っている間、 保護対象のリソースへのアクセスをブロックできます。初期化が終わったら、 ReleaseSemaphoreを使ってカウントを最大値まで増やします。

セマフォ オブジェクトは、 ミューテックス オブジェクトのように所有権を取得できません。ミューテックスを所有するスレッドは、 実行をブロックせずにそのミューテックス オブジェクトを何度でも待機できます。しかし、 1つのセマフォ オブジェクトを何度も待機すると、 待機条件が満たされるたびにセマフォのカウントが減らされ、 カウントが0に達すると実行はブロックされます。同様に、 ReleaseMutex関数を呼び出せるのはミューテックスを所有するスレッドだけですが、 ReleaseSemaphoreは、 カウントを減らすために待機関数を呼び出していない関数でも、 セマフォ オブジェクトのカウントを増やすために呼び出すことができます。

同じセマフォ オブジェクトを指定して待機関数を何度も呼び出すことによって、 セマフォのカウントを2つ以上減らすことができます。しかし、 同じセマフォのハンドルを複数個含む配列を指定してWaitForMultipleObjects関数を呼び出しても、 カウントは1つしか減らされません。

イベント オブジェクト

「イベント オブジェクト」とは、 SetEvent関数やPulseEvent関数を使って明示的にシグナル状態に設定できる同期オブジェクトです。イベント オブジェクトには、 つぎの2種類があります。

オブジェクト 説明

手動リセット イベントResetEvent関数で明示的に非シグナル状態にリセットするまでシグナル状態のままのイベント オブジェクトです。オブジェクトがシグナル状態のときは、 待機中のスレッドやそのオブジェクトを待機関数に指定するスレッドはすべて解放されます。

自動リセット イベント単一の待機スレッドが解放されて、 システムがオブジェクトを自動的に非シグナル状態に設定するまでシグナル状態のままのイベント オブジェクトです。待機中のスレッドがなければ、 イベント オブジェクトはシグナル状態のままです。

イベント オブジェクトは、 特定のイベントが発生したことをスレッドに伝えるときに使います。たとえば、 オーバーラップ入出力の場合、 オーバーラップ操作が完了すると、 システムは、 指定されたイベント オブジェクトをシグナル状態に設定します。1つのスレッドが複数のイベント オブジェクトを同時進行のオーバーラップ操作に指定して、 WaitForMultipleObjects関数を使ってイベント オブジェクトのいずれかがシグナル状態になるのを待つことができます。

イベント オブジェクトを作成するには、 CreateEvent関数を使います。この関数には、 オブジェクトの初期状態と、 手動リセットか自動リセットかを指定します。また、 イベント オブジェクトの名前も指定できます。ほかのプロセスのスレッドは、 この名前を指定してOpenEvent関数を呼び出すことによって、 既存のイベント オブジェクトのハンドルをオープンできます。ミューテックス オブジェクト、 イベント オブジェクト、 セマフォ オブジェクトの名前について詳しくは、 プロセス間の同期を参照してください。

イベント オブジェクトをシグナル状態に設定し、 適切な個数の待機スレッドが解放されてから再びシグナル状態に設定するには、 PulseEvent関数を使います。手動リセット イベント オブジェクトの場合、 すぐに解放可能な待機スレッドはすべて解放されます。自動リセット イベント オブジェクトの場合、 複数のスレッドが待機していても、 この関数は待機スレッドを1つだけ解放します。待機中のスレッドがないときや、 すぐに解放できるスレッドがないときは、 PulseEventはイベント オブジェクトのハンドルを非シグナル状態に設定して戻るだけです。

プロセス間の同期

1つのミューテックスやセマフォ、 イベントなどのオブジェクトのハンドルを複数のプロセスが持つことができるため、 これらのオブジェクトを使ってプロセス間の同期を実現できます。オブジェクトを作成するプロセスは、 作成関数 (CreateEvent CreateMutex CreateSemaphore) が返すハンドルを使います。それ以外のプロセスは、 オブジェクトの名前を使うか、 継承や複製によってオブジェクトのハンドルをオープンできます。

オブジェクトの名前

名前付きオブジェクトによって、 複数のプロセスがオブジェクトのハンドルを簡単に共有できます。オブジェクトを作成するプロセスは、 MAX_PATH文字までの名前を指定でき、 名前には円記号 (\) 以外の文字を含めることができます。プロセスが名前付きのイベント オブジェクトやミューテックス オブジェクト、 セマフォ オブジェクトを作成すると、 ほかのプロセスは、 その名前を使って適切な関数 (OpenEventOpenMutexOpenSemaphore) を呼び出してオブジェクトのハンドルをオープンできます。名前を比較するとき、 大文字と小文字は区別されます。イベント オブジェクト、 セマフォ オブジェクト、 ミューテックス オブジェクト、 ファイル マッピング オブジェクトの名前は同じ名前空間を共有するため、 指定した名前が別の種類の既存のオブジェクトの名前と一致すると、 エラーが発生します。このため、 名前付きオブジェクトを作成するときは、 一意な名前を使い、 関数の戻り値を調べて名前が重複していないか確認してください。

CreateMutex関数は、 指定された名前が既存のミューテックス オブジェクトの名前に一致するとき、 既存のオブジェクトのハンドルを返します。この場合、 CreateMutexの呼び出しはOpenMutex関数の呼び出しと同等です (CreateEvent関数やCreateSemaphore関数も、 既存のイベント オブジェクトやセマフォ オブジェクトに対して同様に動作します)。このため、 複数のプロセスが同じミューテックスに対してCreateMutexを使うのは、 あるプロセスがCreateMutexを呼び出してからほかのプロセスがOpenMutexを呼び出すのと同じです。ただし、 すべてのプロセスがCreateMutexを呼び出す場合、 ミューテックスを作成するプロセスが最初に起動する必要はありません。しかし、 ミューテックス オブジェクトでこの方法を使う場合、 プロセスはミューテックスの所有権をすぐに要求してはいけません。複数のプロセスが所有権をすぐに要求すると、 所有権を最初に取得するプロセスが不定になる可能性があります。

オブジェクトの継承

SECURITY_ATTRIBUTES構造体を継承可能に設定してCreateProcess関数で作成された子プロセスは、 ミューテックスやイベント、 セマフォのハンドルを継承できます。子プロセスが継承するハンドルは、 親プロセスのハンドルと同じアクセスを持っています。継承したハンドルは子プロセスのハンドル テーブルに置かれますが、 そのハンドルを子プロセスが使うには、 親プロセスがハンドルの値を子プロセスに渡さなければなりません。親プロセスは、 CreateProcess関数のコマンド ライン引数としてこの値を渡すことができます。子プロセスは、 GetCommandLine関数を使ってコマンド ライン引数を取得し、 ハンドル引数を利用可能なハンドルに変換できます。オブジェクトの継承について詳しくは、 プロセスとスレッドの概要を参照してください。

オブジェクトの複製

DuplicateHandle関数は、 指定されたほかのプロセスが使うことができるハンドルの複製を作成します。このオブジェクト ハンドル共有方法は、 名前付きオブジェクトや継承を使う方法よりも複雑です。この方法では、 ハンドルを作成するプロセスと複製されたハンドルを使うプロセスの間で通信が必要です。必要な情報 (ハンドルの値やプロセス識別子) は、 プロセス間通信 (名前付きパイプや共有ファイル、 共有メモリなど) で受渡しできます。オブジェクトの複製について詳しくは、 プロセスとスレッドの概要を参照してください。

同期入出力とオーバーラップ入出力

Win32 APIは、 ファイルや名前付きパイプ、 シリアル通信デバイスに対する同期および非同期 (オーバーラップ) の入出力操作をサポートしています。WriteFile関数、 ReadFile関数、 DeviceIoControl関数、 WaitCommEvent関数、 ConnectNamedPipe関数、 TransactNamedPipe関数は、 同期的または非同期的に実行できます。ReadFileEx関数とWriteFileEx関数は、 非同期にしか実行できません。

同期的に実行された関数は、 操作が完了するまで戻りません。つまり、 時間のかかる操作が完了するのを待つ間、 呼び出し側スレッドの実行はブロックされます。オーバーラップ操作用に呼び出された関数は、 操作が完了していなくてもすぐに戻ります。このため、 呼び出し側スレッドは、 時間のかかるI/O操作をバックグラウンドで実行しながら、 ほかの作業を実行できます。たとえば、 1つのスレッドが複数のハンドルを使って複数のI/O操作を同時に実行したり、 1つのハンドルに対して読み取り操作と書き込み操作を同時に実行できます。スレッドの実行とオーバーラップ操作の完了の同期をとるには、 GetOverlappedResult関数か待機関数を使って、 操作が完了したかどうかを判断します。

オーバーラップ操作には、 FILE_FLAG_OVERLAPPEDフラグを指定して作成したファイルや名前付きパイプ、 通信デバイスが必要です。関数をオーバーラップ操作として呼び出すには、 OVERLAPPED構造体を指すポインタを指定します。このポインタがNULLならば、 ハンドルがFILE_FLAG_OVERLAPPEDで作成されていても、 操作は同期的に実行されます。OVERLAPPED構造体には、 (自動リセットではなく) 手動リセット オブジェクトのハンドルを設定しなければなりません。操作が完了する前にI/O関数が戻るとき、 システムは、 イベント オブジェクトを非シグナル状態に設定します。操作が完了すると、 システムは、 イベント オブジェクトをシグナル状態に設定します。

関数をオーバーラップ操作として呼び出したとき、 関数が戻る前に操作が完了する場合があります。このような場合、 操作が同期的に実行されたときと同様に扱われます。しかし、 操作が完了していなければ、 関数の戻り値はFALSEになり、 GetLastError関数はERROR_IO_PENDINGを返します。

スレッドは、 2つの方法のいずれかでオーバーラップ操作を管理できます。GetOverlappedResult関数を使って、 オーバーラップ操作が完了するのを待つことができます。また、 OVERLAPPED構造体に設定した手動リセット イベント オブジェクトのハンドルを待機関数に指定して、 待機関数が戻ってからGetOverlappedResultを呼び出す方法もあります。GetOverlappedResultは、 完了したオーバーラップ操作の結果を返します。また、 実際に転送したバイト数を報告する場合もあります。

複数のオーバーラップ操作を同時に実行するときは、 各操作についてそれぞれ手動リセット イベント オブジェクトを設定したOVERLAPPED構造体を指定しなければなりません。オーバーラップ操作のいずれかが完了するまで待機するには、 各イベント オブジェクトのハンドルをハンドル配列パラメータとしてWaitForMultipleObjects関数 (または複数オブジェクト待機関数のいずれか) を呼び出します。複数オブジェクト待機関数の戻り値はシグナル状態になったイベント オブジェクトを示しているため、 戻り値によってどの操作が完了したかを判断できます。

OVERLAPPED構造体にイベント オブジェクトを指定しなければ、 システムは、 オーバーラップ操作が完了したとき、 ファイルや名前付きパイプ、 通信デバイスをシグナル状態にします。このように、 待機関数には同期オブジェクト以外のハンドルも指定できますが、 このような目的でファイル オブジェクトなどのハンドルを使うと問題が発生する場合があります。同じファイルや名前付きパイプ、 通信デバイスに対して複数のオーバーラップ操作を同時に実行すると、 どの操作によってオブジェクトがシグナル状態になったのかがわからなくなります。このため、 各オーバーラップ操作ごとに個別のイベント オブジェクトを使った方が安全です。

オーバーラップ操作、 完了ルーチン GetOverlappedResult関数の使い方を示した例は、 パイプの概要を参照してください。

クリティカル セクション オブジェクト

「クリティカル セクション」 オブジェクトはミューテックス オブジェクトと同様な同期を実現しますが、 クリティカル セクション オブジェクトは1つのプロセス内のスレッド間でだけ使えます。イベント、 ミューテックス、 セマフォの各オブジェクトも単一プロセス アプリケーションで使えますが、 クリティカル セクション オブジェクトの方が少し高速で、 より効率的な相互排他同期を実現できます。クリティカル セクション オブジェクトは、 ミューテックス オブジェクトと同様、 一度に1つのスレッドしか所有できないため、 共有リソースを同時アクセスから保護するときに使います。たとえば、 プロセスは、 クリティカル セクション オブジェクトを使って、 グローバルなデータ構造体を複数のスレッドが同時に変更しないようにすることができます。

プロセスは、 クリティカル セクション オブジェクトが使うメモリを割り当てなければなりません。通常、 メモリの割り当てに必要なのは、 CRITICAL_SECTION型の変数の宣言だけです。プロセスの各スレッドがクリティカル セクション オブジェクトを使うには、 プロセスがInitializeCriticalSection関数を使ってオブジェクトを初期化しなければなりません。

スレッドは、 EnterCriticalSection関数を使って所有権を要求し、 LeaveCriticalSection関数を使って所有権を解放できます。ほかのスレッドがクリティカル セクションを所有していれば、 EnterCriticalSectionは、 所有権を取得できるまで待ち続けます。ミューテックス オブジェクトで相互排他を実現する場合、 待機関数は指定されたタイム アウト時間が経過すると戻ります。

クリティカル セクション オブジェクトの所有権を取得したスレッドは、 実行をブロックせずにさらにEnterCriticalSectionを呼び出すことができます。このため、 すでに所有しているクリティカル セクション オブジェクトを待機することによるデッドロックが発生することはありません。所有権を解放するには、 クリティカル セクションに入った回数だけLeaveCriticalSectionを呼び出してください。

プロセスのスレッドは、 DeleteCriticalSection関数を使って、 クリティカル セクション オブジェクトを初期化するときに割り当てられたシステム リソースを解放できます。この関数を呼び出した後は、 そのクリティカル セクションを使って同期を行うことはできません。

クリティカル セクション オブジェクトをスレッドのいずれかが所有しているとき、 影響を受けるのは、 EnterCriticalSectionを呼び出してそのオブジェクトの所有権を待機しているスレッドだけです。所有権を待機していないスレッドは、 通常どおり実行を続けることができます。

インターロック変数アクセス

InterlockedIncrement関数とInterlockedDecrement関数によって、 同じプロセス内や別のプロセス内の複数のスレッドが共有する32ビット変数へのアクセスの同期のための簡単な機構を実現できます。この2つの関数は、 変数の増減操作とその結果を調べる操作をまとめたものです。マルチタスク オペレーティング システムでは、 システムがあるスレッドの実行に割り込んでプロセッサ時間をほかのスレッドに割り当てるため、 この関数が役立ちます。このような同期がなければ、 あるスレッドが変数の値を増やして、 その変数の値を調べる前にシステムが割り込む可能性があります。このとき、 別のスレッドが同じ変数の値を増やすと、 最初のスレッドが次にプロセッサ時間を受け取って変数の値を調べると、 値は1回ではなく2回増やされていることになります。インターロック変数アクセス関数によって、 このようなエラーを防ぐことができます。

変数が共有メモリにあるときは、 別のプロセスのスレッドどうしがこの機構を使うことができます。

同期の使用

ここの例では、 次の技法を説明します。

名前付きオブジェクトを使ったプロセス間でのオブジェクトの共有

複数オブジェクトの待機

ミューテックス オブジェクトの使用

セマフォ オブジェクトの使用

イベント オブジェクトの使用

クリティカル セクション オブジェクトの使用

名前付きオブジェクトの使用

次の例は、 オブジェクト名を使って2つのプロセスが同じミューテックス オブジェクトのハンドルをオープンする方法を示しています。

最初のプロセスは、 CreateMutex関数を使ってミューテックス オブジェクトを作成します。この関数は、 同じ名前のオブジェクトがすでに存在していても成功します。

/* One process creates the mutex object. */

HANDLE hMutex;
DWORD dwErr;

hMutex = CreateMutex(
NULL,/* no security descriptor */
FALSE,/* mutex not owned*/
"NameOfMutexObject");/* object name */
if (hMutex == NULL)
printf("CreateMutex error: %d\n", GetLastError() );
else
if ( GetLastError() == ERROR_ALREADY_EXISTS )
printf("CreateMutex opened existing mutex\n");
else
printf("CreateMutex created new mutex\n");

2番目のプロセスは、 OpenMutex関数を使って既存のミューテックス オブジェクトのハンドルをオープンします。指定された名前のミューテックス オブジェクトが存在しなければ、 この関数は失敗します。アクセス パラメータには、 ミューテックス オブジェクトへの完全なアクセスを指定します。待機関数でハンドルを使うには、 完全なアクセスが必要です。

/* Another process opens a handle of the existing mutex. */

HANDLE hMutex;

hMutex = OpenMutex(
MUTEX_ALL_ACCESS,/* request full access*/
FALSE,/* handle not inheritable */
"NameOfMutexObject");/* object name */
if (hMutex == NULL)
printf("OpenMutex error: %d\n", GetLastError() );

次の例は、 CreateSemaphore関数を使って、 オブジェクトがすでに存在するときは失敗する名前付きオブジェクト作成操作を示しています。

HANDLE CreateNewSemaphore(LPSECURITY_ATTRIBUTES lpsa,
LONG cInitial, LONG cMax, LPTSTR lpszName) {
HANDLE hSem;

/* Create or open a named semaphore. */

hSem = CreateSemaphore(
lpsa,/* security attributes */
cInitial,/* initial count*/
cMax,/* maximum count*/
lpszName);/* semaphore name*/

/* Close handle, and return NULL if existing semaphore opened. */

if (hSem != NULL &&
GetLastError() == ERROR_ALREADY_EXISTS) {
CloseHandle(hSem);
return NULL;
}

/* If new semaphore was created, return the handle. */

return hSem;

}

複数のオブジェクトの待機

この例は、 CreateEvent関数を使って2つのイベント オブジェクトを作成します。それから、 WaitForMultipleObjects関数を使って、 オブジェクトのどちらかがシグナル状態になるまで待ちます。

HANDLE hEvents[2];
DWORD i, dwEvent;

/* Create two event objects. */

for (i = 0; i < 2; i++) {
hEvents[i] = CreateEvent(
NULL,/* no security attributes*/
FALSE,/* auto-reset event object*/
FALSE,/* initial state is nonsignaled*/
NULL);/* unnamed object*/
if (hEvents[i] == NULL) {
printf("CreateEvent error: %d\n", GetLastError() );
ExitProcess(0);
}
}

/*
* The creating thread waits for other threads or processes
* to signal the event objects.
*/

dwEvent = WaitForMultipleObjects(
2,/* number of objects in array */
hEvents,/* array of objects*/
FALSE,/* wait for any*/
INFINITE);/* indefinite wait */

/* Return value indicates which event is signaled. */

switch (dwEvent) {

/* hEvent[0] was signaled. */

case WAIT_OBJECT_0 + 0:

/* Perform tasks required by this event. */

break;

/* hEvent[1] was signaled. */

case WAIT_OBJECT_0 + 1:

/* Perform tasks required by this event. */

break;

/* Return value is invalid. */

default:

printf("Wait error: %d\n", GetLastError());
ExitProcess(0);

}

ミューテックス オブジェクトの使用

共有リソースを複数のスレッドやプロセスが同時にアクセスしないようにするには、 ミューテックス オブジェクトを使います。各スレッドは、 共有リソースをアクセスするコードを実行するには、 ミューテックスの所有権を取得しなければなりません。たとえば、 複数のスレッドがデータベースへのアクセスを共有している場合、 ミューテックス オブジェクトを使って、 データベースに書き込めるスレッドを一度に1つに限定できます。

次の例は、 CreateMutex関数を使って名前付きミューテックス オブジェクトを作成したり、 既存のミューテックス オブジェクトのハンドルをオープンします。

HANDLE hMutex;

/* Create a mutex with no initial owner. */

hMutex = CreateMutex(
NULL,/* no security attributes */
FALSE,/* initially not owned*/
"MutexToProtectDatabase");/* name of mutex*/
if (hMutex == NULL) {
/* check for error */
}

このプロセスのスレッドは、 データベースに書き込むとき、 まずミューテックスの所有権を要求します。所有権を取得したら、 データベースに書き込んで、 所有権を解放します。

この例では、 try-finally構造化例外処理構文を使って、 スレッドがミューテックスを正しく解放するようにしています。finallyブロックは、 tryブロックの終了方法に関わらず (tryブロックでTerminateThread関数を呼び出さないかぎり)、 必ず実行されます。これによって、 ミューテックスが不注意に放棄されないようになっています。

BOOL FunctionToWriteToDatabase(HANDLE hMutex) {
DWORD dwWaitResult;

/* Request ownership of mutex. */

dwWaitResult = WaitForSingleObject(
hMutex,/* handle of mutex*/
5000L);/* five-second time-out interval */

switch (dwWaitResult) {

/* The thread got mutex ownership. */

case WAIT_OBJECT_0:

try {

.
. /* Write to the database. */
.

}

finally {

/* Release ownership of the mutex object. */

if (! ReleaseMutex(hMutex)) {
/* Deal with error. */
}

break;
}

/* Cannot get mutex ownership due to time-out. */

case WAIT_TIMEOUT:

return FALSE;

/* Got ownership of the abandoned mutex object. */

case WAIT_ABANDONED:

return FALSE;

}

return TRUE;

}

セマフォ オブジェクトの使用

次の例では、 セマフォ オブジェクトを使って、 プロセスが作成するウィンドウの個数を制限しています。まず、 CreateSemaphore関数を使って、 初期カウントと最大カウントを指定してセマフォを作成します。

HANDLE hSemaphore;
LONG cMax = 10;
LONG cPreviousCount;

/* Create a semaphore with initial and max. counts of 10. */

hSemaphore = CreateSemaphore(
NULL,/* no security attributes */
cMax,/* initial count*/
cMax,/* maximum count*/
NULL);/* unnamed semaphore*/
if (hSemaphore == NULL) {
/* Check for error. */
}

プロセスのスレッドは、 新しいウィンドウを作成する前に、 WaitForSingleObject関数でセマフォの現在のカウントを調べて、 ウィンドウをさらに作成可能かどうか判断します。待機関数のタイム アウト パラメータには0を指定しているため、 セマフォが非シグナル状態ならば関数はすぐに戻ります。

DWORD dwWaitResult;

/* Try to enter the semaphore gate. */

dwWaitResult = WaitForSingleObject(
hSemaphore,/* handle of semaphore*/
0L);/* zero-second time-out interval */

switch (dwWaitResult) {

/* The semaphore object was signaled. */

case WAIT_OBJECT_0:
.
. /* OK to open another window. */
.
break;

/* Semaphore was nonsignaled, so a time-out occurred. */

case WAIT_TIMEOUT:
.
. /* Cannot open another window. */
.
break;

}

スレッドは、 ウィンドウをクローズするとき、 ReleaseSemaphore関数を使ってセマフォのカウントを増やします。

/* Increment the count of the semaphore. */

if (! ReleaseSemaphore(
hSemaphore, /* handle of semaphore*/
1,/* increase count by one*/
NULL) ) {/* not interested in previous count */

/* Deal with the error. */

}

イベント オブジェクトの使用

Win32 APIは、 さまざまな場合にイベント オブジェクトを使ってイベントの発生を待機中のスレッドに通知します。たとえば、 ファイルや名前付きパイプ、 通信デバイスに対するオーバーラップ入出力 (I/O) 操作では、 イベント オブジェクトを使って操作の完了を知らせます。オーバーラップI/O操作でのイベント オブジェクトの使用について詳しくは、 同期入出力とオーバーラップ入出力を参照してください。

次の例では、 イベント オブジェクトを使って、 マスタ スレッドが共有メモリ バッファに書き込んでいる間、 ほかのスレッドがそのバッファを読み取れないようにしています。まず、 マスタ スレッドがCreateEvent関数を使って手動リセット イベント オブジェクトを作成します。マスタ スレッドは、 バッファに書き込むときにイベント オブジェクトを非シグナル状態に設定し、 書き込みが完了するとオブジェクトをシグナル状態にリセットします。それから、 読み取りスレッドをいくつか作成し、 各スレッドについて自動リセット イベント オブジェクトを1つずつ作成します。各読み取りスレッドは、 バッファを読み取っていないときはイベント オブジェクトをシグナル状態に設定します。

#define NUMTHREADS 4

HANDLE hGlobalWriteEvent;

void CreateEventsAndThreads(void) {

HANDLE hReadEvents[NUMTHREADS], hThread;
DWORD i, IDThread;

/*
* Create a manual-reset event object. The master thread sets
* this to nonsignaled when it writes to the shared buffer.
*/

hGlobalWriteEvent = CreateEvent(
NULL,/* no security attributes*/
TRUE,/* manual-reset event*/
TRUE,/* initial state is signaled */
"WriteEvent" /* object name*/
);
if (hGlobalWriteEvent == NULL) {
/* error exit */
}

/*
* Create multiple threads and an auto-reset event object
* for each thread. Each thread sets its event object to
* signaled when it is not reading from the shared buffer.
*/

for(i = 1; i <= NUMTHREADS; i++) {

/* Create the auto-reset event. */

hReadEvents[i] = CreateEvent(
NULL,/* no security attributes*/
FALSE,/* auto-reset event*/
TRUE,/* initial state is signaled */
NULL);/* object not named*/
if (hReadEvents[i] == NULL) {
/* error exit */
}

hThread = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE) ThreadFunction,
&hReadEvents[i], /* pass event handle*/
0, &IDThread);
if (hThread == NULL) {
/* error exit */
}
}

}

マスタ スレッドは、 共有バッファに書き込む前に、 ResetEvent関数を使ってhGlobalWriteEvent(アプリケーション定義のグローバル変数) を非シグナル状態に設定します。これによって、 読み取りスレッドによる読み取り操作の開始はブロックされます。それから、 マスタ スレッドは、 WaitForMultipleObjects関数を呼び出して、 すべての読み取りスレッドが現在の読み取り操作を終了するまで待機します。WaitForMultipleObjectsが戻ると、 マスタ スレッドはバッファに安全に書き込むことができます。マスタ スレッドは、 書き込みが終わったら、 hGlobalWriteEventと読み取りスレッド イベントをすべてシグナル状態に設定して、 読み取りスレッドが読み取り操作を再開できるようにします。

VOID WriteToBuffer(VOID) {
DWORD dwWaitResult, i;

/* Reset hGlobalWriteEvent to nonsignaled, to block readers. */

if (! ResetEvent(hGlobalWriteEvent) ) {
/* error exit */
}

/* Wait for all reading threads to finish reading. */

dwWaitResult = WaitForMultipleObjects(
NUMTHREADS,/* number of handles in array*/
hReadEvents, /* array of read-event handles*/
TRUE,/* wait until all are signaled*/
INFINITE);/* indefinite wait*/

switch (dwWaitResult) {

/* All read-event objects were signaled. */

case WAIT_OBJECT_0:
.
. /* Write to the shared buffer. */
.
break;

/* An error occurred. */

default:

printf("Wait error: %d\n", GetLastError());
ExitProcess(0);

}

/* Set hGlobalWriteEvent to signaled. */

if (! SetEvent(hGlobalWriteEvent) ) {
/* error exit */
}

/* Set all read events to signaled. */

for(i = 1; i <= NUMTHREADS; i++)
if (! SetEvent(hReadEvents[i]) ) {
/* error exit */
}

}

各読み取りスレッドは、 読み取り操作を開始する前に、 WaitForMultipleObjectsを使って、 アプリケーション定義のグローバル変数hGlobalWriteEventと自分自身の読み取りイベントがシグナル状態になるまで待機します。WaitForMultipleObjectsが戻ったとき、 読み取りスレッドの自動リセット イベントは非シグナル状態にリセットされています。このため、 読み取りスレッドがSetEvent関数を使ってイベントをシグナル状態に設定するまで、 マスタ スレッドによるバッファへの書き込みはブロックされます。

VOID ThreadFunction(LPVOID lpParam) {
DWORD dwWaitResult, i;
HANDLE hEvents[2];

hEvents[0] = (HANDLE) *lpParam;/* thread's read event */
hEvents[1] = hGlobalWriteEvent;

dwWaitResult = WaitForMultipleObjects(
2, /* number of handles in array*/
hEvents,/* array of event handles*/
TRUE,/* wait till all are signaled */
INFINITE);/* indefinite wait*/

switch (dwWaitResult) {

/* Both event objects were signaled. */

case WAIT_OBJECT_0:
.
. /* Read from the shared buffer. */
.
break;

/* An error occurred. */

default:

printf("Wait error: %d\n", GetLastError());
ExitThread(0);

}

/* Set the read event to signaled. */

if (! SetEvent(hEvents[0]) ) {
/* error exit */
}

}

クリティカル セクション オブジェクトの使用

次の例は、 スレッドによるクリティカル セクションの初期化、 開始、 終了を示しています。ミューテックスの例 (ミューテックス オブジェクトの使用を参照してください) と同様に、 この例では、 try-finally構造化例外処理構文を使って、 スレッドが必ずLeaveCriticalSection関数を呼び出してクリティカル セクション オブジェクトの所有権を解放するようにしています。

CRITICAL_SECTION GlobalCriticalSection;

/* Initialize the critical section. */

InitializeCriticalSection(&GlobalCriticalSection);

/* Request ownership of the critical section. */

try {

EnterCriticalSection(&GlobalCriticalSection);
.
. /* Access the shared resource. */
.
}
finally {

/* Release ownership of the critical section. */

LeaveCriticalSection(&GlobalCriticalSection);

}

同期関数

同期に関する関数を次に示します。

CreateEvent

CreateMutex

CreateSemaphore

DeleteCriticalSection

EnterCriticalSection

GetOverlappedResult

InitializeCriticalSection

InterlockedDecrement

InterlockedExchange

InterlockedIncrement

LeaveCriticalSection

MsgWaitForMultipleObjects

OpenEvent

OpenMutex

OpenSemaphore

PulseEvent

ReleaseMutex

ReleaseSemaphore

ResetEvent

SetEvent

WaitForMultipleObjects

WaitForMultipleObjectsEx

WaitForSingleObject

WaitForSingleObjectEx

▲ページトップに戻る

【本を読んでもよくらからない】 … 個別指導でわかりやすくお教えします