在多线程环境下,单例模式需要保证在并发访问时仍然只创建一次实例。C++11之后,标准库提供了原子操作和内存序列化机制,结合局部静态变量的特性,可以轻松实现线程安全的单例。下面给出两种常见实现方式,并说明其优缺点。
1. 局部静态变量(Meyer’s Singleton)
class Singleton {
public:
static Singleton& instance() {
static Singleton instance; // 只会初始化一次
return instance;
}
// 禁止复制和移动
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
~Singleton() = default;
};
- 原理:C++11保证局部静态变量在第一次访问时以线程安全的方式初始化。此时只会有一次初始化,后续并发访问不会重复创建对象。
- 优点:实现简洁,几乎无运行时开销,编译器负责同步。
- 缺点:无法在运行时决定单例的生命周期(例如需要在程序结束前显式销毁),且如果单例构造函数抛异常,后续访问会导致再次尝试初始化。
2. 双重检查锁(Double-Checked Locking)
class Singleton {
public:
static Singleton* instance() {
Singleton* tmp = instance_.load(std::memory_order_acquire);
if (!tmp) {
std::lock_guard<std::mutex> lock(mutex_);
tmp = instance_.load(std::memory_order_relaxed);
if (!tmp) {
tmp = new Singleton();
instance_.store(tmp, std::memory_order_release);
}
}
return tmp;
}
// ...
private:
Singleton() = default;
static std::atomic<Singleton*> instance_;
static std::mutex mutex_;
};
std::atomic<Singleton*> Singleton::instance_{nullptr};
std::mutex Singleton::mutex_;
- 原理:先快速检查原子指针是否为空,若不为空直接返回;若为空则进入互斥锁保护的临界区,再次检查后创建实例并更新原子指针。
memory_order_acquire/release保证了内存可见性。 - 优点:可以在需要时显式释放单例(通过删除实例并置空原子指针),在高并发场景下读操作无锁,写操作仅在首次初始化时有锁。
- 缺点:实现复杂度高,错误使用容易导致数据竞争或ABA问题;需要手动管理内存。
3. std::call_once 与 std::once_flag
class Singleton {
public:
static Singleton& instance() {
std::call_once(flag_, [](){ instance_ = new Singleton(); });
return *instance_;
}
private:
Singleton() = default;
static Singleton* instance_;
static std::once_flag flag_;
};
Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::flag_;
- 原理:
std::call_once保证给定的lambda只会执行一次,即使有多个线程并发调用。内部使用std::once_flag管理同步状态。 - 优点:简洁安全,适合需要一次性初始化且不想使用局部静态变量的场景。
- 缺点:与双重检查锁类似,需要手动销毁实例。
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::shared_ptr <Singleton>(new Singleton());
instance_ = sp;
return sp;
}
private:
Singleton() = default;
static std::weak_ptr <Singleton> instance_;
static std::mutex mutex_;
};
std::weak_ptr <Singleton> Singleton::instance_;
std::mutex Singleton::mutex_;
- 特点:允许外部
shared_ptr共享实例,实例在最后一个引用失效时自动销毁,避免手动删除。线程安全由互斥锁保证。
5. 总结
- 推荐使用:如果单例不需要在运行时销毁,最简单且最安全的方案是使用局部静态变量(Meyer’s Singleton)。
- 需要显式销毁或特殊初始化:可考虑
std::call_once或双重检查锁。 - 懒加载并可销毁:使用
std::weak_ptr+shared_ptr+互斥锁。
通过合理选择实现方式,可以在 C++ 中获得高效、线程安全且易维护的单例模式。