在多线程环境下,单例模式需要保证只有一个实例,并且在并发访问时不产生竞争条件。下面介绍几种常见实现方式,并说明各自的优缺点。
1. 经典双重检查锁定(Double-Checked Locking)
class Singleton {
public:
static Singleton& getInstance() {
if (!instance_) { // 第一次检查
std::lock_guard<std::mutex> lock(mutex_);
if (!instance_) { // 第二次检查
instance_.reset(new 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::mutex mutex_;
};
std::unique_ptr <Singleton> Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;
优点:延迟初始化,首次访问时才创建实例。
缺点:在 C++11 之前的编译器可能存在指令重排导致线程安全问题;需要两次检查,略微增加代码复杂度。
2. 局部静态变量(Meyer’s Singleton)
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // 线程安全的局部静态初始化
return instance;
}
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
C++11 标准保证局部静态变量在多线程环境下的初始化是线程安全的(所谓的“魔法”)。这段代码简洁易读,且性能优越。
优点:代码最简洁,完全依赖语言实现保证线程安全。
缺点:实例无法延迟销毁(除非在程序结束时),如果需要手动销毁实例,需要额外实现。
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;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton* instance_;
static std::once_flag flag_;
};
Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::flag_;
std::call_once 保证闭包只被调用一次,线程安全性好。
优点:对初始化过程的控制更细粒度,可在需要时使用自定义构造函数。
缺点:相比 Meyer’s Singleton 稍显冗长。
4. 结合 std::shared_ptr 与 std::weak_ptr
class Singleton {
public:
static std::shared_ptr <Singleton> getInstance() {
std::lock_guard<std::mutex> lock(mutex_);
if (auto sp = instance_.lock()) {
return sp;
}
auto sp = std::make_shared <Singleton>();
instance_ = sp;
return sp;
}
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static std::weak_ptr <Singleton> instance_;
static std::mutex mutex_;
};
std::weak_ptr <Singleton> Singleton::instance_;
std::mutex Singleton::mutex_;
使用 shared_ptr 让单例在没有引用时自动销毁,适用于需要在运行时释放单例的场景。
优点:自动内存管理,线程安全。
缺点:略高的内存开销和运行时开销。
小结
- Meyer’s Singleton:最推荐,代码最简洁,现代 C++ 编译器已内置线程安全。
- 双重检查锁定:兼容老旧编译器,但要注意指令重排。
std::call_once:适合自定义初始化流程。shared_ptr/weak_ptr:需要单例可销毁时使用。
在实际项目中,除非有特殊需求,一般使用 Meyer’s Singleton 即可满足大多数情况的线程安全单例实现。