在 C++ 中实现线程安全的单例模式,最常用的方法是使用局部静态变量或 C++11 的原子操作与互斥锁。下面以几种典型实现方式为例,逐步说明如何保证单例在多线程环境下的安全性。
1. 局部静态变量(Meyers Singleton)
class Singleton {
public:
static Singleton& instance() {
static Singleton inst; // C++11 以后,局部静态变量初始化是线程安全的
return inst;
}
// 禁止拷贝构造和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() {}
~Singleton() {}
};
- 线程安全保证:自 C++11 起,编译器在第一次调用
instance()时会对局部静态变量inst进行线程安全的初始化。若多线程同时进入该函数,只有一个线程会完成初始化,其他线程会等待。 - 优点:实现简洁、延迟加载、无显式锁开销。
- 缺点:在销毁阶段,如果有线程依旧在访问
instance(),可能导致访问已析构对象。
2. 带双重检查锁(Double-Checked Locking)
class Singleton {
public:
static Singleton* getInstance() {
if (!instance_) { // 第一次检查
std::lock_guard<std::mutex> lock(mtx_);
if (!instance_) { // 第二次检查
instance_ = new Singleton();
}
}
return instance_;
}
// ...
private:
Singleton() {}
static Singleton* instance_;
static std::mutex mtx_;
};
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mtx_;
- 线程安全保证:双重检查锁通过先不加锁的检查减少锁竞争,再在锁内再次检查,保证了只有第一次调用会真正创建实例。由于
instance_是指针,且指针写入操作是原子的,C++11 的std::atomic或volatile可以进一步强化。 - 优点:适用于 C++11 以前没有局部静态线程安全的实现。
- 缺点:实现略显复杂,若未使用
std::atomic或正确内存序,可能出现指令重排导致的可见性问题。
3. 使用 std::call_once
class Singleton {
public:
static Singleton& getInstance() {
std::call_once(initFlag_, []() { instance_ = new Singleton(); });
return *instance_;
}
// ...
private:
Singleton() {}
static Singleton* instance_;
static std::once_flag initFlag_;
};
Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;
- 线程安全保证:
std::call_once确保传入的 lambda 只会被调用一次,内部使用的是线程安全的机制来管理一次性初始化。 - 优点:实现简洁、性能良好(只需要一次锁),不受
new的异常影响。 - 缺点:需要手动维护指针,销毁时可能需要手动 delete,或者改为智能指针。
4. 使用 std::shared_ptr 与 std::once_flag
class Singleton {
public:
static std::shared_ptr <Singleton> instance() {
std::call_once(initFlag_, []() {
instance_ = std::shared_ptr <Singleton>(new Singleton());
});
return instance_;
}
// ...
private:
Singleton() {}
static std::shared_ptr <Singleton> instance_;
static std::once_flag initFlag_;
};
std::shared_ptr <Singleton> Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;
- 优点:使用
shared_ptr自动管理生命周期,避免手动 delete。 - 缺点:多次获取实例返回相同的
shared_ptr对象,使用时需注意引用计数。
5. 关键点回顾
- 延迟加载:实例只在第一次访问时创建,节约资源。
- 线程安全:C++11 以后局部静态变量初始化已保证线程安全;否则需使用
std::mutex、std::call_once等同步机制。 - 销毁顺序:若单例在程序结束前需要被销毁,最好使用
std::shared_ptr或std::unique_ptr,或在atexit注册销毁函数。 - 异常安全:若构造函数抛异常,应确保单例指针不被错误地设置。使用
std::call_once或局部静态变量能自动处理。
6. 小结
在 C++ 中实现线程安全的单例模式最推荐的做法是使用局部静态变量(Meyers Singleton),因为它既简单又符合现代 C++ 标准。若需兼容旧标准或更细粒度的控制,可以采用 std::call_once 或双重检查锁实现。只要遵循上述原则,就能在多线程环境下安全、可靠地使用单例模式。