C++ call_once用法
🌟 概念
call_once
是 C++11 引入的并发工具,定义在 <mutex>
头文件中,用于确保某个函数在多线程环境中只被调用一次。它常用于延迟初始化(lazy initialization)或单例模式的线程安全实现。
- 核心功能:保证一个函数在多个线程中只执行一次,即使多个线程同时尝试调用。
- 依赖:
call_once
使用std::once_flag
作为标志,确保函数的单次执行。 - 适用场景:
- 单例对象初始化
- 全局配置加载
- 资源初始化(如数据库连接)
🔧 API
1. std::call_once
template <class Callable, class... Args>
void call_once(std::once_flag& flag, Callable&& func, Args&&... args);
- 参数:
flag
:std::once_flag
对象,用于跟踪函数是否已被调用。func
:要执行的函数(可调用对象,如函数、lambda、函数对象等)。args
:传递给func
的参数。
- 返回值:无。
- 行为:
- 如果
flag
表示函数未被调用,则执行func(args...)
。 - 如果
func
抛出异常,异常会传播,且flag
状态保持未调用,允许下次重试。 - 其他线程在
func
执行完成前会被阻塞。
- 如果
2. std::once_flag
- 定义:
std::once_flag
是一个非复制、非移动的类,用于与call_once
配合。 - 初始化:必须在定义时初始化,通常为静态或全局变量。
- 用法:作为
call_once
的第一个参数。
⚠️ 使用注意
std::once_flag
不能复制或移动,必须确保同一once_flag
对象被所有相关线程使用。call_once
是线程安全的,无需额外加锁。- 如果
func
执行时间长,可能导致其他线程阻塞,需谨慎设计。 - 异常安全性:若
func
抛出异常,call_once
会传播异常,且允许重试。
📦 简单示例
以下是一个使用 call_once
实现线程安全单例的例子:
#include <iostream>
#include <mutex>
#include <thread>class Singleton {
public:static Singleton& getInstance() {std::call_once(flag_, &Singleton::init);return *instance_;}void doSomething() {std::cout << "Singleton is doing something.\n";}private:Singleton() { std::cout << "Singleton constructed.\n"; }static void init() {instance_ = new Singleton();}static Singleton* instance_;static std::once_flag flag_;
};// 静态成员初始化
Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::flag_;void worker() {Singleton& s = Singleton::getInstance();s.doSomething();
}int main() {std::thread t1(worker);std::thread t2(worker);std::thread t3(worker);t1.join();t2.join();t3.join();return 0;
}
示例输出
Singleton constructed.
Singleton is doing something.
Singleton is doing something.
Singleton is doing something.
说明
getInstance
使用call_once
确保init
只被调用一次,从而保证Singleton
对象只构造一次。- 多个线程调用
getInstance
时,只有第一个线程执行init
,其他线程等待。 doSomething
展示了单例的使用。
✅ 优点
- 线程安全,无需手动加锁。
- 延迟初始化,节省资源。
- 异常安全,支持重试。
🚫 局限性
- 如果初始化函数耗时长,可能影响性能。
std::once_flag
不可复制,需小心管理。