在多线程环境下,单例模式的实现需要保证:
- 只创建一次实例;
- 对所有线程可见且可安全访问;
- 采用最小的锁开销。
1. 经典实现:Meyers Singleton(C++11 之后)
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. 双重检查锁(DCL)实现(兼容 C++98)
class Singleton {
public:
static Singleton* instance() {
Singleton* tmp = instance_;
if (!tmp) {
std::lock_guard<std::mutex> lock(mutex_);
tmp = instance_;
if (!tmp) {
tmp = new Singleton();
instance_ = tmp;
}
}
return tmp;
}
// ...
private:
Singleton() = default;
~Singleton() = default;
static Singleton* instance_;
static std::mutex mutex_;
};
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;
双重检查锁避免了在每次访问时都加锁,但必须确保 instance_ 的可见性。C++11 引入了 std::atomic,可以进一步保证可见性:
static std::atomic<Singleton*> instance_{nullptr};
3. 使用 std::call_once
class Singleton {
public:
static Singleton& instance() {
std::call_once(flag_, []{ instance_ = new Singleton(); });
return *instance_;
}
// ...
private:
Singleton() = default;
~Singleton() = default;
static Singleton* instance_;
static std::once_flag flag_;
};
Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::flag_;
std::call_once 与 std::once_flag 组合提供了线程安全的单次初始化,且实现细节隐藏,代码更简洁。
4. 总结
- C++11 以上:推荐使用 Meyers Singleton,代码最简洁、性能最佳。
- C++98:可以使用双重检查锁或
std::call_once(若提供线程库)。 - 可见性:如果使用原始指针,建议配合
std::atomic或volatile,以避免编译器优化导致的可见性问题。 - 销毁:单例一般不需要显式销毁,程序结束时自动释放;若需要显式销毁,需额外实现析构逻辑。
通过上述方法,你可以在多线程 C++ 程序中安全地实现单例模式,既满足线程安全,又保持代码简洁。