在多线程环境下,单例模式的实现需要确保即使有多个线程同时请求实例,也只能生成一个实例。C++11 之后,标准提供了线程安全的局部静态变量初始化机制,这可以直接用来实现单例。下面给出几种常见实现方式,并讨论各自的优缺点。
1. 局部静态变量实现(推荐)
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // C++11 之后线程安全
return instance;
}
// 删除拷贝构造和赋值运算符
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() {} // 私有构造函数
~Singleton() {}
};
- 优点
- 代码简洁、易读。
- 依赖于 C++11 的线程安全局部静态变量初始化,几乎不需要额外的锁。
- 对象在第一次使用时才创建,符合延迟加载的需求。
- 缺点
- 对象在程序退出时才被析构,若在
atexit之前有线程正在使用,可能导致悬空引用。 - 如果需要显式销毁实例(如想在程序某处释放资源),则需要额外的机制。
- 对象在程序退出时才被析构,若在
2. 带双重检查锁(双检锁)实现
class Singleton {
public:
static Singleton* getInstance() {
if (!instance_) {
std::lock_guard<std::mutex> lock(mutex_);
if (!instance_) {
instance_ = new Singleton();
}
}
return instance_;
}
static void destroyInstance() {
std::lock_guard<std::mutex> lock(mutex_);
delete instance_;
instance_ = nullptr;
}
private:
Singleton() {}
~Singleton() {}
static Singleton* instance_;
static std::mutex mutex_;
};
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;
- 优点
- 可在程序任意位置手动销毁实例,避免资源泄漏。
- 缺点
- 代码复杂。
- 需要保证多次检查的正确性,若实现不当会产生指令重排导致的“非线程安全”问题。
- 在 C++11 之后,
std::atomic与std::memory_order可进一步提高安全性,但仍需谨慎。
3. Meyers 单例 + std::unique_ptr
如果想在单例被销毁时执行更复杂的清理逻辑,可以使用 std::unique_ptr 配合自定义析构:
class Singleton {
public:
static Singleton& getInstance() {
static std::unique_ptr <Singleton> instance(new Singleton());
return *instance;
}
static void reset() {
getInstance(); // 确保实例已创建
// 可执行自定义清理逻辑
}
private:
Singleton() {}
~Singleton() {}
};
- 优点
- 可通过
reset()执行清理,避免静态对象的销毁顺序问题。
- 可通过
- 缺点
- 仍然依赖于局部静态变量的线程安全性,无法手动完全控制实例生命周期。
4. 线程局部单例(Thread‑Local Singleton)
如果每个线程都需要自己的单例实例,可使用 thread_local:
class ThreadSingleton {
public:
static ThreadSingleton& getInstance() {
thread_local ThreadSingleton instance;
return instance;
}
private:
ThreadSingleton() {}
~ThreadSingleton() {}
};
- 优点
- 每个线程都有独立实例,避免跨线程竞争。
- 缺点
- 适用于需要线程隔离的场景,不能共享状态。
小结
- 推荐:使用 C++11 线程安全的局部静态变量实现(Meyers Singleton)。
- 特殊需求:若需要手动销毁或更复杂的生命周期管理,可考虑双重检查锁或
unique_ptr方案。 - 线程隔离:使用
thread_local可为每个线程提供独立实例。
以上实现均满足 C++ 标准库的线程安全特性,避免了传统 pthread_mutex 或 std::mutex 的细粒度锁管理。根据项目需求选择合适的实现方式,即可在多线程环境下安全、可靠地使用单例模式。