在多线程环境下,单例模式需要保证对象只被创建一次,同时确保所有线程都能安全地访问该实例。下面介绍几种常见的实现方式,并给出完整代码示例。
1. 经典 Meyers 单例
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // C++11 保证线程安全
return instance;
}
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
C++11 起,函数内部的 static 对象在首次调用时初始化,且初始化过程是线程安全的。只要编译器支持 C++11 或更高标准,就可以直接使用这种方式。
2. 显式互斥锁
如果你想兼容 C++98 或者需要更细粒度的控制,可以使用 std::mutex:
#include <mutex>
class Singleton {
public:
static Singleton& getInstance() {
std::call_once(initFlag, [](){ 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::once_flag initFlag;
};
std::unique_ptr <Singleton> Singleton::instance;
std::once_flag Singleton::initFlag;
这里 std::call_once 确保 instance 只被初始化一次,避免多线程竞争。
3. 双重检查锁(Double-Check Locking)
双重检查锁是一种性能优化的做法,但在 C++ 中实现时需要注意 std::atomic 或 volatile:
#include <atomic>
class Singleton {
public:
static Singleton* getInstance() {
Singleton* 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 Singleton;
instance.store(tmp, std::memory_order_release);
}
}
return tmp;
}
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static std::atomic<Singleton*> instance;
static std::mutex mtx;
};
std::atomic<Singleton*> Singleton::instance{nullptr};
std::mutex Singleton::mtx;
需要确保对 instance 的访问使用合适的内存序,避免编译器优化导致的不可预期行为。
4. 静态成员对象(模块化单例)
如果单例需要在不同编译单元共享,可以将实例放在独立的源文件:
// Singleton.h
class Singleton {
public:
static Singleton& instance();
void doSomething();
private:
Singleton() = default;
~Singleton() = default;
};
// Singleton.cpp
#include "Singleton.h"
Singleton& Singleton::instance() {
static Singleton s;
return s;
}
void Singleton::doSomething() {
// ...
}
因为 static 对象在 instance() 函数内部,编译器会处理线程安全。
小结
- C++11 及以上:使用 Meyers 单例最简洁且安全。
- 兼容旧标准:使用
std::call_once或std::mutex的显式锁。 - 性能优化:双重检查锁需配合
std::atomic与正确的内存序。
选择哪种实现方式取决于项目对标准支持、性能需求以及代码可读性的考虑。只要遵循以上原则,线程安全的单例模式就能轻松实现。