在 C++ 中实现单例模式(Singleton)时,最常见的挑战之一就是保证多线程环境下的线程安全。下面将通过几种常见方案来实现线程安全的单例,并比较它们的优缺点。
1. 经典的双重检查锁(Double-Check Locking)
#include <mutex>
class Singleton {
public:
static Singleton& instance() {
if (!ptr) { // 第一次检查(无锁)
std::lock_guard<std::mutex> lock(mtx);
if (!ptr) { // 第二次检查(加锁)
ptr = new Singleton();
}
}
return *ptr;
}
// 禁止拷贝构造和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
static Singleton* ptr;
static std::mutex mtx;
};
Singleton* Singleton::ptr = nullptr;
std::mutex Singleton::mtx;
优点
- 仅在第一次访问时创建实例,后续访问不需要加锁,性能较好。
缺点
- 对
new的顺序保证依赖编译器和硬件实现(C++11 的std::atomic与内存模型可解决)。 - 代码较为冗长,易出错。
2. 局部静态变量(Meyers Singleton)
class Singleton {
public:
static Singleton& instance() {
static Singleton instance; // C++11 起线程安全
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
};
优点
- 代码简洁,易于维护。
- C++11 以后编译器保证局部静态变量初始化是线程安全的。
缺点
- 对销毁顺序有一定要求(如果在
atexit时出现循环依赖,可能导致段错误)。
3. std::call_once 与 std::once_flag
#include <mutex>
class Singleton {
public:
static Singleton& instance() {
std::call_once(initFlag, [](){ ptr = new Singleton(); });
return *ptr;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
static Singleton* ptr;
static std::once_flag initFlag;
};
Singleton* Singleton::ptr = nullptr;
std::once_flag Singleton::initFlag;
优点
- 明确表示一次性初始化,语义清晰。
- 兼容旧标准(C++03 通过 Boost 实现)。
缺点
- 仍需要手动管理
ptr的销毁。
4. 通过 std::shared_ptr 自动销毁
#include <memory>
#include <mutex>
class Singleton {
public:
static std::shared_ptr <Singleton> instance() {
std::call_once(initFlag, [](){ ptr = std::make_shared <Singleton>(); });
return ptr;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
static std::shared_ptr <Singleton> ptr;
static std::once_flag initFlag;
};
std::shared_ptr <Singleton> Singleton::ptr = nullptr;
std::once_flag Singleton::initFlag;
优点
- 自动管理内存,避免手动 delete。
- 支持多线程安全的构造和销毁。
缺点
std::shared_ptr产生一定的性能开销,尤其在高频访问场景中。
5. 现代 C++ 方案:std::unique_ptr 与懒加载
class Singleton {
public:
static Singleton& instance() {
static std::unique_ptr <Singleton> instance{
std::make_unique <Singleton>()
};
return *instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
};
优点
- 结合
unique_ptr与局部静态变量,既安全又高效。 - 对象销毁时机与程序结束一致。
小结
- Meyers Singleton(局部静态变量)是最推荐的方案,代码最简洁且线程安全。
- 对于需要显式销毁或更复杂生命周期管理的情况,可考虑
std::call_once+std::shared_ptr或unique_ptr。 - 在旧编译器或 C++11 前标准下,
std::call_once与std::once_flag是最安全的实现方式。
实践建议:除非有特殊需求(如需要在单例构造时做复杂逻辑,或需要在运行时手动销毁),否则直接使用 Meyers Singleton。如果担心销毁顺序问题,可在 atexit 里手动销毁 std::shared_ptr 管理的实例。