在多线程环境下,单例模式需要确保即使多个线程同时访问,仍然只会产生一个实例。C++11 引入了线程安全的局部静态变量初始化,使得实现单例变得简单而可靠。以下是一种常见的实现方式,并对关键点做详细说明。
1. 基础单例实现
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // C++11 规定此初始化是线程安全的
return instance;
}
// 禁止复制构造和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 示例方法
void doSomething() {
std::cout << "Singleton instance address: " << this << std::endl;
}
private:
Singleton() {
std::cout << "Singleton constructor called." << std::endl;
}
~Singleton() = default;
};
关键点说明
static Singleton instance;在函数内部的局部静态变量。C++11 规定第一次进入时的初始化是原子操作,后续访问会被std::call_once机制保护,避免多线程竞争。- 删除复制构造和赋值运算符,防止外部拷贝导致多实例。
- 析构函数默认即可,若需要自定义清理逻辑,可以在
~Singleton()中实现。
2. 延迟初始化与自销毁
有时你希望单例在首次使用时才真正创建,并在程序结束后自动销毁。上面的实现已满足此需求。若需更细粒度的控制,可以结合 std::unique_ptr 与 std::atomic:
class LazySingleton {
public:
static LazySingleton& getInstance() {
LazySingleton* tmp = instance.load(std::memory_order_acquire);
if (!tmp) {
std::lock_guard<std::mutex> lock(mtx);
tmp = instance.load(std::memory_order_relaxed);
if (!tmp) {
tmp = new LazySingleton();
instance.store(tmp, std::memory_order_release);
std::atexit(&LazySingleton::destroy);
}
}
return *tmp;
}
private:
LazySingleton() { std::cout << "LazySingleton constructed\n"; }
~LazySingleton() { std::cout << "LazySingleton destroyed\n"; }
static void destroy() { delete instance.load(std::memory_order_relaxed); }
static std::atomic<LazySingleton*> instance;
static std::mutex mtx;
};
std::atomic<LazySingleton*> LazySingleton::instance{nullptr};
std::mutex LazySingleton::mtx;
std::atomic用于避免在多线程中出现未定义行为。std::atexit保证在程序正常退出时释放资源。
3. 线程安全的懒汉式实现(双重检查锁定)
如果你更熟悉经典的双重检查锁定(Double-Check Locking,DCL),可以这样实现:
class DCLSingleton {
public:
static DCLSingleton* getInstance() {
if (instance == nullptr) { // 第一次检查
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) { // 第二次检查
instance = new DCLSingleton();
std::atexit(&DCLSingleton::destroy);
}
}
return instance;
}
private:
DCLSingleton() { std::cout << "DCLSingleton constructed\n"; }
~DCLSingleton() { std::cout << "DCLSingleton destroyed\n"; }
static void destroy() { delete instance; }
static std::atomic<DCLSingleton*> instance;
static std::mutex mtx;
};
std::atomic<DCLSingleton*> DCLSingleton::instance{nullptr};
std::mutex DCLSingleton::mtx;
注意:在 C++11 之后,使用局部静态变量的方式更简洁、可靠。DCL 需要确保编译器遵循内存模型,否则仍可能出现可见性问题。
4. 单例中的资源管理
单例往往需要管理全局资源,如数据库连接、日志系统等。推荐将这些资源封装为类成员,并在单例构造时初始化:
class LoggerSingleton {
public:
static LoggerSingleton& getInstance() {
static LoggerSingleton instance;
return instance;
}
void log(const std::string& msg) {
std::lock_guard<std::mutex> lock(logMutex);
std::ofstream out(logFile, std::ios::app);
out << msg << std::endl;
}
private:
LoggerSingleton() : logFile("app.log") { std::cout << "Logger initialized\n"; }
~LoggerSingleton() = default;
std::string logFile;
std::mutex logMutex;
};
logMutex确保多线程写日志时不会出现内容交叉。- 使用
std::ofstream的 RAII 机制自动关闭文件。
5. 单元测试注意事项
测试单例时需小心状态共享。可以在测试框架中提供 Test Fixture,在 SetUp()/TearDown() 中重置单例状态,或使用 std::unique_ptr 手动销毁实例(如果实现允许)。例如:
TEST(LoggerTest, LogMessage) {
LoggerSingleton& logger = LoggerSingleton::getInstance();
logger.log("Test message");
// 验证日志文件中是否存在该行
}
6. 结语
C++11 及以后版本为线程安全单例提供了最简洁的实现方式:使用局部静态变量即可。若业务对初始化时机或销毁顺序有更严格要求,可结合 std::atomic、std::mutex 与 std::atexit 进行自定义实现。通过合理封装资源、加锁与 RAII,单例模式既能保持全局唯一,又能在多线程环境中保持稳定与高效。