在多线程环境下,单例模式的实现尤为重要,因为它必须保证在任何时刻只有一个实例存在,并且在并发情况下不会出现竞态条件。以下是一种常用且高效的实现方式,结合了 C++11 之后的特性:std::call_once 与 std::once_flag。
1. 基本思路
- 懒汉式:实例在第一次使用时才创建,避免不必要的资源占用。
- 线程安全:利用
std::call_once确保单次初始化,即使多个线程同时请求也只会执行一次构造。 - 懒加载:通过局部静态变量或
std::unique_ptr延迟初始化。
2. 代码实现
#include <iostream>
#include <mutex>
#include <memory>
class Singleton {
public:
// 提供全局访问点
static Singleton& Instance() {
// std::call_once 只会执行一次
std::call_once(initFlag_, []() {
instance_.reset(new Singleton);
});
return *instance_;
}
// 业务方法示例
void doSomething() const {
std::cout << "Singleton instance address: " << this << std::endl;
}
// 禁止拷贝构造和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() {
std::cout << "Singleton constructed at " << this << std::endl;
}
~Singleton() = default;
// 单例实例
static std::unique_ptr <Singleton> instance_;
// 用于控制一次初始化
static std::once_flag initFlag_;
};
// 静态成员定义
std::unique_ptr <Singleton> Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;
关键点说明
-
std::call_once与std::once_flag
std::call_once接受一个std::once_flag以及一个可调用对象(lambda、函数指针等)。无论有多少线程调用Instance(),initFlag_只会让其中一个线程执行 lambda,保证单例唯一性。 -
懒加载
` 只在 `call_once` 里实例化,避免在程序启动时就创建。
`std::unique_ptr -
线程安全的析构
如果你需要在程序结束时销毁单例,使用std::unique_ptr可以自动在全局静态对象析构时销毁实例。若你想手动控制生命周期,可在Singleton内部实现destroy()方法,使用std::call_once确保只销毁一次。 -
禁止拷贝/赋值
为了确保唯一性,删除拷贝构造和赋值运算符。
3. 使用示例
#include <thread>
void worker() {
Singleton::Instance().doSomething();
}
int main() {
std::thread t1(worker);
std::thread t2(worker);
std::thread t3(worker);
t1.join();
t2.join();
t3.join();
return 0;
}
运行结果将显示同一实例地址,证明只有一个实例被创建。
4. 进阶:C++17 以上的局部静态变量
C++11 之后,局部静态变量的初始化已是线程安全的。因此可以更简洁地实现:
class Singleton {
public:
static Singleton& Instance() {
static Singleton instance;
return instance;
}
// 其余同上
};
这种方式省略了 std::call_once,编译器保证线程安全。缺点是如果实例构造抛异常,后续访问会导致再次尝试初始化,可能会产生不确定行为。若构造函数不抛异常,推荐使用此方式。
5. 小结
- 使用
std::call_once与std::once_flag可以在 C++11 以上安全实现线程安全单例。 - 通过
std::unique_ptr或局部静态变量完成懒加载与自动析构。 - 删除拷贝/赋值以保持唯一性。
- 对于性能敏感的场景,可考虑直接使用局部静态变量,因其在多数编译器中实现更高效。
这样,你就可以在多线程 C++ 程序中安全、简洁地使用单例模式了。