懒汉式单例默认线程不安全,因多线程可能同时通过 instance == nullptr 判断并执行 new,导致多次构造;C++11 起推荐用 std::call_once + std::once_flag 或函数内局部静态变量实现线程安全初始化。

懒汉式单例为什么默认线程不安全
因为 getInstance() 第一次调用时才创建实例,多个线程可能同时判断 instance == nullptr 为真,接着各自执行 new 操作——结果构造函数被调用多次,内存泄漏且行为未定义。
C++11 起推荐用 std::call_once + std::once_flag
这是最清晰、可读性强、且由标准库保证只执行一次的方案。不需要手动加锁,也不依赖编译器对局部静态变量的实现细节。
class Singleton {
public:
static Singleton& getInstance() {
std::call_once(initFlag, []() {
instance = new Singleton();
});
return *instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
static Singleton* instance;
static std::once_flag initFlag;
};
Singleton* Singleton::instance = nullptr;
std::once_flag Singleton::initFlag;
-
std::call_once内部已做原子控制,多线程并发调用不会重复执行 lambda - 必须把
instance声明为static Singleton*,不能是局部静态变量(否则无法配合call_once管理生命周期) - 析构需自行管理(比如在程序退出前显式
delete instance),否则会内存泄漏
更简洁但隐含依赖的写法:C++11 局部静态变量
如果不需要手动控制析构时机,直接用函数内局部静态变量是最简方案——C++11 标准规定其初始化是线程安全的。
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // C++11 起,首次调用时线程安全地构造
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
};
别踩 double-checked locking 的坑
手写双重检查锁(DCLP)在 C++ 中极易出错,除非你非常清楚内存序和编译器重排规则。常见错误包括:
立即学习“C++免费学习笔记(深入)”;
- 忘记用
std::atomic声明instance,导致读写非原子 - 漏掉
memory_order_acquire/release,使构造函数指令被重排到指针赋值之后 - 即使加了
volatile也无效——C++ 的volatile不提供线程同步语义
除非有特殊限制(比如不能用 C++11+ 特性),否则不要自己实现 DCLP。
实际项目里优先选局部静态变量写法;若必须延迟析构或兼容旧环境,再用 std::call_once 方案。两者都比手写锁可靠得多。










