在多线程环境下,单例模式的实现需要考虑并发访问问题,避免因竞争导致多个实例被创建。下面给出几种常见的线程安全单例实现方式,并说明各自的优缺点。
1. 经典的双重检查锁(Double-Checked Locking)
#include <mutex>
class Singleton {
public:
static Singleton& getInstance() {
if (!instance_) { // 第一次检查
std::lock_guard<std::mutex> lock(mutex_);
if (!instance_) { // 第二次检查
instance_ = new Singleton();
}
}
return *instance_;
}
// 禁止拷贝构造和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
~Singleton() = default;
static Singleton* instance_;
static std::mutex mutex_;
};
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;
优点:在大多数情况下只需要一次锁操作,性能相对较好。
缺点:需要注意内存模型和编译器优化,尤其在C++11之前的编译器可能导致“懒加载”失效。使用std::atomic或std::call_once可以避免这些问题。
2. std::call_once 与 std::once_flag
C++11 标准提供了更安全、更简洁的单例实现方式:
#include <mutex>
class Singleton {
public:
static Singleton& getInstance() {
std::call_once(initFlag_, []() {
instance_ = new Singleton();
});
return *instance_;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
~Singleton() = default;
static Singleton* instance_;
static std::once_flag initFlag_;
};
Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;
优点:std::call_once 确保初始化代码只会执行一次,且对所有线程都是可见的。代码更简洁,且不需要显式的锁。
缺点:需要手动管理实例的销毁(可通过atexit或使用 std::unique_ptr)。如果你想让实例在程序结束时自动析构,可以改为返回一个 std::unique_ptr 或使用局部静态变量(见下文)。
3. 局部静态变量(Meyer’s Singleton)
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;
~Singleton() = default;
};
优点:代码最短、最直观。自 C++11 起,局部静态变量的初始化是线程安全的,且对象在程序结束时自动析构。
缺点:如果你需要在特定时机销毁单例,或者想要延迟销毁(例如在某个线程结束时),就需要更复杂的管理。
4. 线程安全的懒加载与销毁
如果你既想保持延迟初始化,又想手动控制销毁,可以结合 std::shared_ptr 与自定义销毁器:
#include <memory>
#include <mutex>
class Singleton {
public:
static std::shared_ptr <Singleton> getInstance() {
std::call_once(initFlag_, []() {
instance_ = std::shared_ptr <Singleton>(new Singleton(), [](Singleton* ptr){
delete ptr; // 自定义销毁器
std::cout << "Singleton destroyed\n";
});
});
return instance_;
}
private:
Singleton() = default;
~Singleton() = default;
static std::shared_ptr <Singleton> instance_;
static std::once_flag initFlag_;
};
std::shared_ptr <Singleton> Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;
此实现既保持了 call_once 的安全性,又能在 shared_ptr 的引用计数归零时销毁实例。
小结
- 双重检查锁:性能好,但需谨慎实现。
std::call_once:推荐使用,代码简洁且安全。- 局部静态变量:最简洁的实现,适合大多数情况。
- 自定义销毁:适用于需要手动控制生命周期的场景。
在实际项目中,推荐优先使用 std::call_once 或局部静态变量的实现,除非有特殊需求。这样可以避免多线程下的竞争条件,保证单例实例的唯一性与线程安全。