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() 的执行。
在多线程程序设计中,确保某些初始化操作只执行一次是一个常见的需求。例如,在创建单例对象或加载配置数据时,我们必须确保即使多个线程尝试同时执行这些操作,初始化也只会发生一次,以避免资源竞争和数据不一致。
为了解决这一问题,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() 会执行传入的函数,并将标记设置为已执行,保证此后的调用不会再次执行该函数;
- 如果已执行过,则std::call_once不会执行传入的函数。
这种机制的优点是线程安全的,而且不需要显示地使用互斥锁,因为 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; }这种模式特别适用于以下场景:
- 单例模式初始化:当设计模式需要确保一个类只有一个实例时,初始化这个实例的函数可以通过 std::call_once() 保证线程安全;
- 一次性配置加载:如从文件或网络加载配置数据,可以确保无论多少线程需要这些数据,加载操作都只执行一次。