在多线程环境下,单例模式(Singleton)是一种常见的设计模式,它保证一个类只有一个实例,并为全局提供访问点。实现线程安全的单例模式,既要保证对象只被实例化一次,又要避免因竞争条件导致多线程间的访问冲突。下面从经典实现、C++11后的改进以及性能优化三方面展开讨论。
1. 经典实现:双重检查锁(Double‑Check Locking)
class Singleton {
public:
static Singleton& instance() {
if (!ptr) { // 第一次检查(无锁)
std::lock_guard<std::mutex> lock(mtx); // 进入临界区
if (!ptr) { // 第二次检查(有锁)
ptr = new Singleton();
}
}
return *ptr;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
static Singleton* ptr;
static std::mutex mtx;
};
Singleton* Singleton::ptr = nullptr;
std::mutex Singleton::mtx;
缺点
- 性能损耗:每次访问都需要进行一次锁操作,即使实例已创建也会有一次无谓的锁检查。
- 可移植性问题:在旧版编译器下,
new的返回值可能不是完全初始化的内存,导致数据竞争。 - 缺少销毁机制:单例对象在程序结束前不会被自动销毁,可能导致资源泄露。
2. C++11 之后的推荐做法:函数内部静态变量
自 C++11 起,局部静态变量的初始化是线程安全的。只需写一个 getInstance 函数,返回局部静态对象的引用即可。
class Singleton {
public:
static Singleton& instance() {
static Singleton instance; // 线程安全初始化
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
};
优点
- 简洁易读:只需一行代码实现单例。
- 天然线程安全:编译器保证在第一次访问时只初始化一次。
- 资源自动释放:程序退出时静态对象会被销毁,释放资源。
注意事项
- 若单例需要在析构时做特殊操作(例如释放外部资源),可在类中自定义析构函数。
- 在多进程环境下,每个进程拥有独立的单例实例,不能共享。
3. 延迟销毁(Lazy Destruction)
在某些应用场景下,需要在程序终止前显式销毁单例,避免析构顺序导致的资源访问冲突。可以使用 std::unique_ptr 与自定义销毁函数:
class Singleton {
public:
static Singleton& instance() {
std::call_once(flag, [](){
ptr.reset(new Singleton);
std::atexit([](){ ptr.reset(); });
});
return *ptr;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
static std::unique_ptr <Singleton> ptr;
static std::once_flag flag;
};
std::unique_ptr <Singleton> Singleton::ptr = nullptr;
std::once_flag Singleton::flag;
std::call_once 保障只调用一次,std::atexit 在程序退出前销毁单例。
4. 性能优化
如果单例对象只读且使用频繁,建议使用 std::shared_ptr 或 std::atomic 加速访问:
class Singleton {
public:
static std::shared_ptr <Singleton> instance() {
std::shared_ptr <Singleton> tmp = ptr.load(std::memory_order_acquire);
if (!tmp) {
std::lock_guard<std::mutex> lock(mtx);
tmp = ptr.load(std::memory_order_relaxed);
if (!tmp) {
tmp = std::make_shared <Singleton>();
ptr.store(tmp, std::memory_order_release);
}
}
return tmp;
}
private:
Singleton() = default;
static std::atomic<std::shared_ptr<Singleton>> ptr;
static std::mutex mtx;
};
std::atomic提供了无锁读取,减少竞争。- 由于
std::shared_ptr采用引用计数,内存管理更灵活。
5. 总结
- C++11 及以后:首选局部静态变量实现,简洁且线程安全。
- 需要控制销毁顺序:结合
std::call_once+std::atexit。 - 高频访问:可使用
std::atomic<std::shared_ptr>优化读取性能。
通过上述方法,既能确保单例的唯一性,又能兼顾多线程安全与性能,是现代 C++ 开发中的最佳实践。