在多线程环境下,单例模式需要保证同一时刻只能有一个实例被创建,并且在多个线程同时访问时不会出现竞争条件。下面通过几种典型实现方式,介绍如何在C++中编写线程安全的单例。
1. 局部静态变量(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;
};
C++11之后的编译器会在第一次调用getInstance()时以线程安全的方式初始化instance。这是一种最简洁、最安全的实现。
2. 双重检查锁(DCLP)
class Singleton {
public:
static Singleton* getInstance() {
if (!instance) { // 第一检查
std::lock_guard<std::mutex> lock(mtx);
if (!instance) { // 第二检查
instance = new Singleton();
}
}
return instance;
}
private:
Singleton() = default;
static Singleton* instance;
static std::mutex mtx;
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;
双重检查锁可以减少锁的使用次数,但需要注意内存模型和对象构造的可见性。C++11提供了std::atomic可用于保证可见性。
3. std::call_once 与 std::once_flag
class Singleton {
public:
static Singleton& getInstance() {
std::call_once(flag, [](){ instance = new Singleton(); });
return *instance;
}
private:
Singleton() = default;
static Singleton* instance;
static std::once_flag flag;
};
Singleton* Singleton::instance = nullptr;
std::once_flag Singleton::flag;
std::call_once保证给定的初始化函数只会被调用一次,内部使用了线程安全的原子操作,适合需要显式控制初始化逻辑的场景。
4. 静态局部变量 + std::shared_ptr
class Singleton {
public:
static std::shared_ptr <Singleton> getInstance() {
static std::shared_ptr <Singleton> instance(new Singleton(), [](Singleton*){});
return instance;
}
private:
Singleton() = default;
};
使用std::shared_ptr可以在程序退出时自动释放资源,避免悬空指针。
5. Meyers单例与延迟销毁
局部静态变量在程序退出时会自动销毁,避免了手动释放。若想控制销毁时机,可以结合std::unique_ptr与自定义析构器。
6. 线程安全的懒汉式与饿汉式比较
| 特性 | 懒汉式(如getInstance()) |
饿汉式(在程序启动时创建) |
|---|---|---|
| 延迟加载 | ✔ | ✘ |
| 内存占用 | 需要时才占用 | 立即占用 |
| 初始化顺序 | 线程安全(C++11) | 无竞争问题 |
| 可测试性 | 可能导致单例在单元测试中不易重置 | 简单 |
小结
- 对于大多数现代C++项目,局部静态变量(Meyers单例)是最推荐的实现方式,简洁且线程安全。
- 若需要自定义销毁逻辑或懒加载后手动释放,
std::call_once或双重检查锁是可选方案,但实现更复杂。 - 在设计单例时,也要考虑单例对象的生命周期、资源管理和测试友好性,避免单例导致的全局状态耦合。
掌握上述几种实现后,你就能在多线程C++项目中安全、灵活地使用单例模式。