首页 > 编程笔记 > C++笔记 阅读:8

C++ std::call_once()的用法(附带实例)

在并发环境中,正确地实现延迟初始化是非常重要的,这可以避免不必要的计算和资源消耗,同时确保线程安全。

在多线程程序设计中,确保某些初始化操作只执行一次是一个常见的需求。例如,在创建单例对象或加载配置数据时,我们必须确保即使多个线程尝试同时执行这些操作,初始化也只会发生一次,以避免资源竞争和数据不一致。

为了解决这一问题,C++11 标准引入了 std::once_flag 和 std::call_once() 这两个工具,它们配合使用可以保证指定函数在多线程环境中只被执行一次,即使有多个线程同时到达执行点。

std::once_flag 是一个不透明的数据结构,用于存储函数是否已经被调用的状态。每个 std::once_flag 实例都只与一个需要被保证只执行一次的函数关联。std::once_flag 应当与需要单次执行的函数或者代码块保持相同的生命周期,通常作为静态状态存在于全局或者作为某个对象的一部分。一旦被 std::call_once() 标记为已调用,它的状态就不会再变更,确保生命周期的管理不会影响其功能。

std::call_once() 是一个模板函数,接受一个 std::once_flag 和一个可调用对象(如函数、Lambda 表达式、函数对象等)。std::call_once() 将检查关联的 std::once_flag 是否已被标记为执行过:
这种机制的优点是线程安全的,而且不需要显示地使用互斥锁,因为 std::call_once() 内部已经处理了所有必要的同步操作。这样,开发者可以更加简洁地编写安全的初始化代码,而无须担心复杂的同步和竞争条件。

在下面示例中,将定义一个全局资源 resource 和一个初始化函数 init_resource(),后者假设执行一些复杂的初始化操作(在这个例子中,简单地设置 resource 的值为 77)。我们使用 std::once_flag 变量 flag 来控制 init_resource() 的执行。
#include <iostream>
#include <mutex>
#include <thread>
std::once_flag flag;
int resource;
void init_resource() {
    resource = 77; // 假设这是一项复杂的初始化操作
    std::cout << "Resource initialized.\n";
}
void thread_func() {
    std::call_once(flag, init_resource);
    std::cout << "Resource: " << resource << std::endl;
}
int main() {
    std::thread t1(thread_func);
    std::thread t2(thread_func);
    t1.join();
    t2.join();
    return 0;
}
这种模式特别适用于以下场景:

相关文章