在多线程环境中,单例模式常常需要保证在所有线程中只创建一次实例,并且保证线程安全。下面介绍几种实现方式,并说明它们的优缺点。
1. 使用 C++11 的 std::call_once
#include <iostream>
#include <mutex>
class Singleton {
public:
static Singleton& getInstance() {
std::call_once(initFlag, [](){
instance.reset(new Singleton);
});
return *instance;
}
void doSomething() { std::cout << "Doing something\n"; }
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static std::unique_ptr <Singleton> instance;
static std::once_flag initFlag;
};
std::unique_ptr <Singleton> Singleton::instance = nullptr;
std::once_flag Singleton::initFlag;
int main() {
auto& s1 = Singleton::getInstance();
auto& s2 = Singleton::getInstance();
s1.doSomething();
return 0;
}
优点
- 线程安全:
std::call_once只在第一次调用时执行一次初始化。 - 延迟初始化:只有第一次调用
getInstance()时才会创建实例。 - 简洁:不需要手动加锁,避免死锁和性能损耗。
缺点
- 需要 C++11 及以上标准支持。
2. 饿汉式(Eager Initialization)
class Singleton {
public:
static Singleton& getInstance() {
return instance;
}
private:
Singleton() = default;
static Singleton instance;
};
Singleton Singleton::instance{};
- 线程安全性:在 C++11 之后,静态对象的初始化是线程安全的。
- 缺点:实例在程序启动时就会创建,可能会导致资源浪费或死锁问题。
3. 双重检查锁(Double-Check Locking)
class Singleton {
public:
static Singleton* getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
private:
Singleton() = default;
static Singleton* instance;
static std::mutex mtx;
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;
- 优点:延迟初始化,首次进入时不需要锁,后续访问更快。
- 缺点:在 C++11 之前,可能出现指令重排导致的线程安全问题。即使在 C++11 之后,也需要使用
std::atomic或std::mutex来保证可见性。
4. Meyers 单例(函数内部静态对象)
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
private:
Singleton() = default;
};
- 简洁且安全:C++11 保证了局部静态变量初始化的线程安全。
- 延迟:只有第一次调用
getInstance()时才会初始化。
5. 通过 std::shared_ptr 与 std::call_once
class Singleton {
public:
static std::shared_ptr <Singleton> getInstance() {
std::call_once(initFlag, [](){
instance = std::make_shared <Singleton>();
});
return instance;
}
private:
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;
- 优点:返回智能指针,自动管理生命周期,避免手动 delete。
- 缺点:需要注意循环引用问题。
小结
- 推荐使用:C++11 及以上时,
std::call_once或 Meyers 单例是最简洁、安全的实现方式。 - 性能:Meyers 单例在大多数实现中已足够高效,只有极端高频访问时才考虑微调。
- 跨平台:上述实现都依赖标准库,具有良好的可移植性。
在实际项目中,最重要的是保持代码简洁、可读,并且在多线程环境下确保正确性。根据项目的具体需求(如是否需要手动销毁、资源占用是否可接受等)选择合适的实现方式。