C++ 中的 std::call_once

2025年5月14日 | 阅读3分钟

在 C++ 中,std::call_once 函数确保指定的函数只执行一次,即使来自不同线程的许多并发调用同时发生。当一个线程使用带有特定标志和函数的 std::call_once 时,它会检查是否有其他线程正在执行相同的操作。如果没有正在执行,则该线程调用函数 fn 并传递参数 args。如果另一个线程已经使用相同的标志调用 std::call_once,则当前线程进入被动状态。在此状态下,它等待当前执行完成。

一旦活动执行完成,无论成功与否,被动线程都会返回,而不执行 'fn'。

重要的是,活动执行的所有可见副作用都会在所有带有相同标志的并发调用之间同步。如果活动调用以异常结束且存在被动执行,则会选择其中一个被动执行作为新的活动调用。当活动执行完成后,所有当前的被动执行和未来带有相同标志的调用都会立即返回,而不会成为活动执行。这确保函数只执行一次,从而在多线程环境中保持线程安全和同步。

语法

它具有以下语法:

参数

  • 标志:“flag”是一个 std::once_flag 对象。此标志用于协调给定函数在多个线程中的执行。
  • Fn:“fn”表示一个可调用对象(例如函数指针、函数对象或 lambda 表达式),它应该执行一次。
  • Args:“args”是将传递给函数 fn 的参数。

返回值

异常

  • 如果传递给 std::call_once 的函数或可调用对象抛出异常,则该异常将传播到调用 std::call_once 的线程。
  • 但是,如果发生异常,则 std::once_flag 不被视为已初始化。后续使用相同 std::once_flag 的 std::call_once 调用将再次尝试该函数。

要点

  • 如果对 std::call_once 的多次调用传递了不同的函数 'f',则不清楚哪个 'f' 将被调用。提供的函数在与传递给它的 std::call_once 调用相同的线程上执行。
  • 即使从多个线程调用,函数局部静态变量也保证只初始化一次,它们可能比使用 std::call_once 的等效代码更有效。
  • 此方法与 POSIX 系统上的 pthread_once 类似。

示例

让我们举一个例子来说明 C++ 中的 std::call_once() 函数

输出

 
Initializing shared resource by thread: 124622399161920
Shared resource initialized to 42 by thread: 124622399161920
Thread 124622399161920 is using the shared resource: 42
Thread 124622390769216 is using the shared resource: 42
Thread 124622248158784 is using the shared resource: 42
Thread 124622382376512 is using the shared resource: 42
Thread 124622301296192 is using the shared resource: 42
All threads have completed their work.   

说明

此代码使用 std::call_once 函数来确保共享资源只初始化一次,即使被多个线程访问。initialize_resource 函数使用模拟延迟初始化共享资源(shared_resource),其执行由 std::once 选项控制。main 函数中生成的每个线程都调用 access_shared_resource,其中 std::call_once 确保初始化逻辑只执行一次,无论有多少线程正在运行。后续访问资源的线程使用已初始化的值,而无需重复设置步骤。这保证了线程安全、一次性初始化,并展示了高效的多线程编程。