1 回限りの初期化

コンポーネントは、多くの場合、読み込まれるときではなく、最初に呼び出されるときに初期化タスクを実行するように設計されています。 1 回限りの初期化関数を使用すると、複数のスレッドがこの初期化を試みる場合でも、1 回だけ初期化が実行されます。

Windows Server 2003 および Windows XP: アプリケーションは、Interlocked 関数または他の同期メカニズムを使用して、1 回限りの初期化のために独自の同期を提供する必要があります。 1 回限りの初期化関数は、Windows Vista および Windows Server 2008 以降で使用できます。

1 つのスレッドだけで初期化が実行されるようにするために、1 回限りの初期化関数には次の大きなメリットがあります。

  • 速度のために最適化されます。
  • それらを必要とするプロセッサ アーキテクチャに適切なバリアを形成します。
  • ロックされた、および並列の初期化の両方をサポートします。
  • コードが非同期的または同期的に動作できるように、内部ロックを回避します。

システムは、データと状態の情報を含む不透明な INIT_ONCE 構造体を使用して、初期化プロセスを管理します。 呼び出し元は、この構造体を割り当て、(構造体を動的に初期化するために) InitOnceInitialize を呼び出すか、(構造体を静的に初期化するために) 定数 INIT_ONCE_STATIC_INIT を構造体変数に割り当てることによって初期化します。 最初は、1 回限りの初期化構造体に格納されているデータは NULL で、その状態は初期化されていません。

1 回限りの初期化構造体をプロセス間で共有することはできません。

初期化を実行するスレッドは、必要に応じて、初期化の完了後に呼び出し元が使用できるコンテキストを設定できます。 このコンテキストは、同期オブジェクトにすることも、値またはデータ構造体にすることもできます。 コンテキストが値の場合、その下位 INIT_ONCE_CTX_RESERVED_BITS は 0 である必要があります。 コンテキストがデータ構造体の場合、そのデータ構造体は DWORD でアラインされている必要があります。 コンテキストは、InitOnceBeginInitialize または InitOnceExecuteOnce 関数の lpContext 出力パラメーターで呼び出し元に返されます。

1 回限りの初期化は、同期的または非同期的に実行できます。 オプションのコールバック関数は、同期的な 1 回限りの初期化に使用できます。

同期的な 1 回限りの初期化

次の手順では、コールバック関数を使用しない、同期的な 1 回限りの初期化について説明します。

  1. InitOnceBeginInitialize 関数を正常に呼び出す最初のスレッドが、1 回限りの初期化を開始させます。 同期的な 1 回限りの初期化の場合、INIT_ONCE_ASYNC フラグを指定せずに InitOnceBeginInitialize を呼び出す必要があります。
  2. 初期化を試みる後続のスレッドは、最初のスレッドによる初期化が完了または失敗するまでブロックされます。 最初のスレッドが失敗すると、次のスレッドが初期化を試みることができるようになります (以後、同様)。
  3. 初期化が完了すると、スレッドは InitOnceComplete 関数を呼び出します。 スレッドは、必要に応じて同期オブジェクト (または、その他のコンテキスト データ) を作成し、InitOnceComplete 関数の lpContext パラメーターでそれを指定することができます。
  4. 初期化に成功した場合は、1 回限りの初期化構造体の状態が初期化済みに変更され、lpContext ハンドル (存在する場合) が初期化構造体に格納されます。 後続の初期化の試行では、このコンテキスト データが返されます。 初期化に失敗した場合、データは NULL になります。

次の手順では、コールバック関数を使用する、同期的な 1 回限りの初期化について説明します。

  1. InitOnceExecuteOnce 関数を正常に呼び出す最初のスレッドが、アプリケーション定義の InitOnceCallback コールバック関数へのポインターと、コールバック関数で必要なデータを渡します。 呼び出しが成功すると、InitOnceCallback コールバック関数が実行されます。
  2. 初期化を試みる後続のスレッドは、最初のスレッドによる初期化が完了または失敗するまでブロックされます。 最初のスレッドが失敗すると、次のスレッドが初期化を試みることができるようになります (以後、同様)。
  3. 初期化が完了すると、コールバック関数が返されます。 コールバック関数は、必要に応じて同期オブジェクト (または、その他のコンテキスト データ) を作成し、その Context 出力パラメーターでそれを指定できます。
  4. 初期化に成功した場合は、1 回限りの初期化構造体の状態が初期化済みに変更され、Context ハンドル (存在する場合) が初期化構造体に格納されます。 後続の初期化の試行では、このコンテキスト データが返されます。 初期化に失敗した場合、データは NULL になります。

非同期的な 1 回限りの初期化

次の手順では、非同期的な 1 回限りの初期化について説明します。

  1. 複数のスレッドが同時に、INIT_ONCE_ASYNC が指定された InitOnceBeginInitialize を呼び出して初期化を試みると、fPending パラメーターが TRUE に設定され、すべてのスレッドでこの関数が成功します。 実際に初期化に成功するのは 1 つのスレッドだけで、同時に行われるその他の試行では初期化の状態が変わりません。
  2. InitOnceBeginInitialize が返されたとき、fPending パラメーターに初期化の状態が示されます。
    • fPendingFALSE の場合、1 つのスレッドが初期化に成功しています。 他のスレッドは、作成したコンテキスト データをクリーンアップし、InitOnceBeginInitializelpContext 出力パラメーターのコンテキスト データを使用する必要があります。
    • fPendingTRUE の場合は、初期化がまだ完了していないため、他のスレッドが続行する必要があります。
  3. 各スレッドは InitOnceComplete 関数を呼び出します。 スレッドは、必要に応じて同期オブジェクト (または、その他のコンテキスト データ) を作成し、InitOnceCompletelpContext パラメーターでそれを指定することができます。
  4. InitOnceComplete が返されるとき、その戻り値に、呼び出し元スレッドが初期化に成功したかどうかが示されます。
    • InitOnceComplete が成功した場合、呼び出し元スレッドは初期化に成功しています。 1 回限りの初期化構造体の状態が初期化済みに変更され、lpContext ハンドル (存在する場合) が初期化構造体に格納されます。
    • InitOnceComplete が失敗した場合、別のスレッドが初期化に成功しています。 呼び出し元スレッドは、作成したコンテキスト データをクリーンアップし、INIT_ONCE_CHECK_ONLY が指定された InitOnceBeginInitialize を呼び出して、1 回限りの初期化構造体に格納されているコンテキスト データを取得する必要があります。

複数のサイトから 1 回限りの初期化を呼び出す

1 つの INIT_ONCE 構造体によって保護された 1 回限りの初期化が、複数のサイトから実行される可能性があります。各サイトから異なるコールバックが渡される可能性があり、コールバックが使用される同期と使用されないものとが混在する可能性があります。 それでも、初期化が 1 回だけ正常に実行されることが保証されます。

ただし、非同期的および同期的な初期化を混在させることはできません。非同期的な初期化を試みてから、同期的な初期化を開始しようとすると失敗します。

1 回限りの初期化の使用