在多线程环境下,单例模式需要保证两个重要属性:
- 全局唯一性 – 仅有一个实例。
- 线程安全 – 同一时间只有一个线程能够创建实例,其余线程要么等待,要么直接获取已创建的实例。
下面给出几种常见实现方式,并说明其优缺点。
1. 双重检查锁(Double-Checked Locking,DCL)
class Singleton {
public:
static Singleton& instance() {
if (!ptr_) { // 第一次检查
std::lock_guard<std::mutex> lock(mtx_);
if (!ptr_) { // 第二次检查
ptr_ = new Singleton();
}
}
return *ptr_;
}
private:
Singleton() = default;
~Singleton() { delete ptr_; }
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton* ptr_;
static std::mutex mtx_;
};
Singleton* Singleton::ptr_ = nullptr;
std::mutex Singleton::mtx_;
优点
- 第一次创建后,后续访问不需要加锁,性能好。
- 在大多数现代编译器(GCC、Clang、MSVC)和 C++11 以上的内存模型下可以安全使用。
缺点
- 代码略显复杂,易出错。
- 在旧编译器或使用
std::atomic但没有std::memory_order_acquire/release的情况下仍可能出现重排序导致的可见性问题。
2. Meyers 单例(函数内部静态局部变量)
class Singleton {
public:
static Singleton& instance() {
static Singleton obj; // C++11 之后保证线程安全
return obj;
}
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
优点
- 代码最简洁。
- C++11 标准保证局部静态变量的初始化是线程安全的。
- 不需要手动释放,生命周期由程序结束自动管理。
缺点
- 在某些嵌入式环境或特殊编译器实现中,初始化可能是懒加载,导致第一次调用性能略低。
- 对于需要自定义销毁顺序的情况,控制权不够细致。
3. std::call_once + std::unique_ptr
class Singleton {
public:
static Singleton& instance() {
std::call_once(initFlag_, [](){
instancePtr_ = std::unique_ptr <Singleton>(new Singleton());
});
return *instancePtr_;
}
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static std::once_flag initFlag_;
static std::unique_ptr <Singleton> instancePtr_;
};
std::once_flag Singleton::initFlag_;
std::unique_ptr <Singleton> Singleton::instancePtr_;
优点
- 只需要一次初始化,内部使用
std::once_flag保证原子性。 - 结合
unique_ptr可自动管理实例生命周期。 - 兼容 C++03 只需使用 Boost 的
boost::call_once。
缺点
- 对于极端高并发场景,
std::call_once可能在某些实现中会略微慢于静态局部变量的初始化。
4. 对象池实现(延迟销毁)
如果单例需要在程序结束前显式销毁,或在多进程共享内存中使用,可以把单例包装在对象池里:
class Singleton {
public:
static Singleton& get() {
static SingletonPool pool;
return pool.instance();
}
private:
struct SingletonPool {
SingletonPool() { inst = new Singleton(); }
~SingletonPool() { delete inst; }
Singleton* instance() { return inst; }
Singleton* inst;
};
// ...
};
选型建议
| 场景 | 推荐实现 |
|---|---|
| C++11+ 简单应用 | Meyers 单例 |
| 对销毁顺序有特殊需求 | std::call_once + unique_ptr |
| 旧编译器不支持 C++11 | 双重检查锁(DCL)配合 std::atomic |
| 嵌入式系统,资源极限 | 静态局部变量(Meyers) |
| 多进程共享内存 | 对象池 + 映射 |
小结
C++ 的标准库在 2011 年之后提供了足够的原语(std::mutex, std::call_once, std::atomic)来实现线程安全的单例。最推荐的方式是 Meyers 单例,因为其代码最简洁、性能最优,并且在现代编译器下天然支持线程安全。若需更细粒度的控制(如销毁顺序、跨进程共享),可以考虑 std::call_once 或对象池实现。无论哪种方式,记得 删除拷贝构造和赋值运算符,以保持全局唯一性。