单例模式(Singleton)是软件设计模式之一,用于保证一个类只有一个实例,并提供全局访问点。虽然单例的概念很简单,但在多线程环境中实现线程安全却是一门学问。下面以 C++11 及以上标准为基础,给出几种常见且高效的线程安全实现方式,并说明它们的优缺点。
1. Meyer’s 单例(局部静态变量)
class ThreadSafeSingleton {
public:
static ThreadSafeSingleton& instance() {
static ThreadSafeSingleton instance; // C++11 保证初始化线程安全
return instance;
}
// 禁止复制和移动
ThreadSafeSingleton(const ThreadSafeSingleton&) = delete;
ThreadSafeSingleton& operator=(const ThreadSafeSingleton&) = delete;
ThreadSafeSingleton(ThreadSafeSingleton&&) = delete;
ThreadSafeSingleton& operator=(ThreadSafeSingleton&&) = delete;
private:
ThreadSafeSingleton() = default;
~ThreadSafeSingleton() = default;
};
- 优点:实现最简洁,编译器/运行时自动保证初始化线程安全;不存在显式锁。
- 缺点:无法控制对象销毁顺序(尤其是跨库退出时),且无法延迟加载(除非使用
std::call_once包装)。
2. std::call_once + std::unique_ptr
class ThreadSafeSingleton {
public:
static ThreadSafeSingleton& instance() {
std::call_once(initFlag, []() {
instancePtr.reset(new ThreadSafeSingleton);
});
return *instancePtr;
}
ThreadSafeSingleton(const ThreadSafeSingleton&) = delete;
ThreadSafeSingleton& operator=(const ThreadSafeSingleton&) = delete;
ThreadSafeSingleton(ThreadSafeSingleton&&) = delete;
ThreadSafeSingleton& operator=(ThreadSafeSingleton&&) = delete;
private:
ThreadSafeSingleton() = default;
~ThreadSafeSingleton() = default;
static std::unique_ptr <ThreadSafeSingleton> instancePtr;
static std::once_flag initFlag;
};
std::unique_ptr <ThreadSafeSingleton> ThreadSafeSingleton::instancePtr = nullptr;
std::once_flag ThreadSafeSingleton::initFlag;
- 优点:明确控制实例创建和销毁,避免了静态变量的全局析构问题。
- 缺点:代码略显繁琐,性能略低于 Meyer’s 单例(额外的
call_once机制)。
3. 双重检查锁(Double-Check Locking)
在 C++11 之前,双重检查锁常被用来延迟初始化单例。但在 C++11 之后,使用 std::atomic 可以保证可见性,下面给出一种较为安全的实现:
class ThreadSafeSingleton {
public:
static ThreadSafeSingleton& instance() {
ThreadSafeSingleton* tmp = instancePtr.load(std::memory_order_acquire);
if (!tmp) {
std::lock_guard<std::mutex> lock(initMutex);
tmp = instancePtr.load(std::memory_order_relaxed);
if (!tmp) {
tmp = new ThreadSafeSingleton;
instancePtr.store(tmp, std::memory_order_release);
}
}
return *tmp;
}
ThreadSafeSingleton(const ThreadSafeSingleton&) = delete;
ThreadSafeSingleton& operator=(const ThreadSafeSingleton&) = delete;
private:
ThreadSafeSingleton() = default;
~ThreadSafeSingleton() = default;
static std::atomic<ThreadSafeSingleton*> instancePtr;
static std::mutex initMutex;
};
std::atomic<ThreadSafeSingleton*> ThreadSafeSingleton::instancePtr(nullptr);
std::mutex ThreadSafeSingleton::initMutex;
- 优点:延迟初始化,避免不必要的锁开销。
- 缺点:实现复杂,容易出现错误(尤其是内存可见性问题),不如
std::call_once简单可靠。
4. 现代 C++ 方案:std::shared_ptr + std::call_once
如果单例需要在多处被共享引用,或者需要手动控制引用计数,下面的方案很合适:
class ThreadSafeSingleton {
public:
static std::shared_ptr <ThreadSafeSingleton> instance() {
std::call_once(initFlag, []() {
instancePtr = std::shared_ptr <ThreadSafeSingleton>(new ThreadSafeSingleton, &deleter);
});
return instancePtr;
}
ThreadSafeSingleton(const ThreadSafeSingleton&) = delete;
ThreadSafeSingleton& operator=(const ThreadSafeSingleton&) = delete;
private:
ThreadSafeSingleton() = default;
~ThreadSafeSingleton() = default;
static void deleter(ThreadSafeSingleton* p) {
delete p;
}
static std::shared_ptr <ThreadSafeSingleton> instancePtr;
static std::once_flag initFlag;
};
std::shared_ptr <ThreadSafeSingleton> ThreadSafeSingleton::instancePtr = nullptr;
std::once_flag ThreadSafeSingleton::initFlag;
- 优点:可共享实例,允许外部显式释放;兼容
shared_ptr的生命周期管理。 - 缺点:需要注意
deleter的实现,避免双重删除。
小结
- Meyer’s 单例是最常用且最简洁的实现方式,适用于大多数情况。
std::call_once+unique_ptr或shared_ptr可用于需要更细粒度控制对象生命周期的场景。- 双重检查锁在 C++11 之后需要使用原子操作保证可见性,使用时务必小心。
- 对于跨线程、跨进程的共享数据,建议使用更高层次的同步原语(如
std::mutex、std::shared_mutex)配合单例,避免单例成为并发瓶颈。
根据项目的具体需求与代码风格,选择最合适的实现方案即可。祝你编码愉快!