一次性初始化

组件通常设计为在首次调用时(而不是在加载时)执行初始化任务。 一次性初始化函数可确保此初始化只发生一次,即便可能有多个线程尝试初始化。

Windows Server 2003 和 Windows XP:应用程序必须使用互锁函数或其他同步机制为一次性初始化提供自己的同步。 一次性初始化函数从 Windows Vista 和 Windows Server 2008 开始可用。

一次性初始化函数具有显著优势,可确保只有一个线程执行初始化:

  • 它们针对速度进行了优化。
  • 它们在需要它们的处理器体系结构上创建了适当的屏障。
  • 它们支持锁定初始化和并行初始化。
  • 它们避免了内部锁定,以便代码可以异步或同步运行。

系统通过包含数据和状态信息的不透明 INIT_ONCE 结构来管理初始化过程。 调用方会分配此结构,并通过调用 InitOnceInitialize(动态初始化该结构)或将常量 INIT_ONCE_STATIC_INIT 分配给结构变量(静态初始化该结构)来初始化它。 最初,存储在一次性初始化结构中的数据为 NULL,其状态为“未初始化”。

一次性初始化结构不能跨进程共享。

执行初始化的线程可以选择设置一个在初始化完成后可供调用方使用的上下文。 该上下文可以是同步对象,也可以是值或数据结构。 如果该上下文是一个值,则其低位 INIT_ONCE_CTX_RESERVED_BITS 必须为零。 如果该上下文是数据结构,则数据结构必须按 DWORD 对齐。 该上下文会返回给调用方,它在 InitOnceBeginInitializeInitOnceExecuteOnce 函数的 lpContext 输出参数中。

一次性初始化可以同步或异步执行。 可选的回调函数可用于同步的一次性初始化。

同步的一次性初始化

以下步骤描述不使用回调函数的同步的一次性初始化。

  1. 成功调用 InitOnceBeginInitialize 函数的第一个线程会使一次性初始化启动。 对于同步的一次性初始化,必须调用 InitOnceBeginInitialize 且不带 INIT_ONCE_ASYNC 标志。
  2. 尝试初始化的后续线程将被阻止,直到第一个线程完成初始化或失败。 如果第一个线程失败,则允许下一个线程尝试初始化,以此类推。
  3. 初始化完成时,线程将调用 InitOnceComplete 函数。 线程可以选择创建同步对象(或其他上下文数据),并在 InitOnceComplete 函数的 lpContext 参数中指定它。
  4. 如果初始化成功,则一次性初始化结构的状态将更改为“已初始化”,且 lpContext 句柄(如果有)会存储在初始化结构中。 后续初始化尝试会返回此上下文数据。 如果初始化失败,则数据为 NULL

以下步骤描述使用回调函数的同步的一次性初始化。

  1. 成功调用 InitOnceExecuteOnce 函数的第一个线程会将指针传递给应用程序定义的 InitOnceCallback 回调函数以及回调函数所需的任何数据。 如果调用成功,则将执行 InitOnceCallback 回调函数。
  2. 尝试初始化的后续线程将被阻止,直到第一个线程完成初始化或失败。 如果第一个线程失败,则允许下一个线程尝试初始化,以此类推。
  3. 初始化完成时,将返回回调函数。 回调函数可以选择创建同步对象(或其他上下文数据),并在其 Context 输出参数中指定它。
  4. 如果初始化成功,则一次性初始化结构的状态将更改为“已初始化”,且 Context 句柄(如果有)会存储在初始化结构中。 后续初始化尝试会返回此上下文数据。 如果初始化失败,则数据为 NULL

异步的一次性初始化

以下步骤描述异步的一次性初始化。

  1. 如果多个线程同时尝试通过调用带有 INIT_ONCE_ASYNCInitOnceBeginInitialize 来启动初始化,则该函数在 fPending 参数设置为 TRUE 的所有线程中都会成功。 在初始化时,实际上只有一个线程会成功,其他并发尝试不会更改初始化状态。
  2. InitOnceBeginInitialize 返回时,fPending 参数指示初始化状态:
    • 如果 fPendingFALSE,则初始化时有一个线程已成功。 其他线程应清理他们创建的任何上下文数据,并使用 InitOnceBeginInitializelpContext 输出参数中的上下文数据。
    • 如果 fPendingTRUE,则初始化尚未完成,其他线程应继续。
  3. 每个线程会调用 InitOnceComplete 函数。 线程可以选择创建同步对象(或其他上下文数据),并在 InitOnceCompletelpContext 参数中指定它。
  4. InitOnceComplete 返回时,其返回值指示调用线程在初始化时是否成功。
    • 如果 InitOnceComplete 成功,则调用线程在初始化时已成功。 一次性初始化结构的状态更改为“已初始化”,且 lpContext 句柄(如果有)存储在初始化结构中。
    • 如果 InitOnceComplete 失败,则初始化时另一个线程已成功。 调用线程应清理它创建的任何上下文数据,并调用带有 INIT_ONCE_CHECK_ONLYInitOnceBeginInitialize 以检索存储在一次性初始化结构中的任何上下文数据。

从多个站点调用一次性初始化

由单个 INIT_ONCE 结构保护的一次性初始化可以从多个站点执行,可从每个站点传递不同的回调,可以混合带和不带回调的同步。 初始化仍保证仅成功执行一次。

但是,异步和同步初始化不能混合:尝试异步初始化后,启动同步初始化的尝试会失败。

使用一次性初始化