在多线程环境下,单例模式的实现尤为重要。下面将展示一种利用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;
void doSomething() {
// 业务逻辑
}
private:
Singleton() = default; // 私有构造函数
~Singleton() = default; // 私有析构函数
};
关键点解析
-
局部静态变量
static Singleton instance;位于getInstance()内部。C++11 起,编译器会保证此对象在第一次访问时线程安全地完成构造。- 不需要手动加锁,代码简洁。
-
删除拷贝/移动构造
- 防止外部复制或移动导致多实例。
-
懒加载
- 只有第一次调用
getInstance()时才创建实例,节省资源。
- 只有第一次调用
2. 传统双重检查锁(Double-Check Locking)
在 C++11 之前的代码库中,常见的做法是使用双重检查锁来实现线程安全的单例:
class Singleton {
public:
static Singleton* getInstance() {
if (instance_ == nullptr) {
std::lock_guard<std::mutex> lock(mutex_);
if (instance_ == nullptr) {
instance_ = new Singleton();
}
}
return instance_;
}
// 其他成员...
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton* instance_;
static std::mutex mutex_;
};
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;
缺陷
- 内存屏障与可见性:在某些平台上,
instance_的更新可能不会立即在其他线程可见,导致“野指针”。 - 额外锁开销:即使实例已创建,每次访问都要尝试获取锁,虽然大多数实现使用了“只读”锁来优化,但仍然多了一层不必要的开销。
3. 只读锁的优化
如果你需要在频繁读取单例的环境下减少锁竞争,可以使用读写锁(如 std::shared_mutex):
#include <shared_mutex>
class Singleton {
public:
static Singleton& getInstance() {
std::shared_lock<std::shared_mutex> readLock(mutex_);
if (!instance_) {
readLock.unlock();
std::unique_lock<std::shared_mutex> writeLock(mutex_);
if (!instance_) {
instance_ = std::make_unique <Singleton>();
}
}
return *instance_;
}
// 其他成员...
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static std::unique_ptr <Singleton> instance_;
static std::shared_mutex mutex_;
};
std::unique_ptr <Singleton> Singleton::instance_ = nullptr;
std::shared_mutex Singleton::mutex_;
4. 何时使用哪种实现?
| 场景 | 推荐实现 | 说明 |
|---|---|---|
| 需要最少代码量,且编译器支持 C++11 | 懒汉式局部静态 | 简洁、线程安全 |
| 需要懒加载并且想显式控制锁 | 双重检查锁 | 兼容老编译器 |
| 需要在高并发读场景下减少锁竞争 | 读写锁 + 双重检查 | 适用于读多写少的单例 |
5. 小结
- C++11 的局部静态变量提供了最简单、最安全的单例实现。
- 传统双重检查锁虽兼容老版本,但存在可见性与锁开销问题。
- 读写锁可在特定读多写少的场景下进一步优化。
在实际项目中,建议优先使用 C++11 的局部静态实现,除非有特殊性能需求或兼容性限制。