在多线程环境下实现单例模式,最关键的是保证实例在任何线程里都只被创建一次,同时不产生性能瓶颈。下面给出几种常用的实现方式,并讨论它们的优缺点。
1. C++11 后的静态局部变量
class Singleton {
public:
static Singleton& instance() {
static Singleton obj; // C++11 之后的初始化是线程安全的
return obj;
}
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
-
优点
- 代码简洁,几乎没有额外开销。
- 编译器保证线程安全(C++11 标准中对函数内部静态局部变量的初始化是原子且只执行一次)。
- 延迟初始化(仅在第一次调用时创建)。
-
缺点
- 需要 C++11 或更高版本。
- 如果实例需要在全局析构时做特殊清理,可能会出现“static deinitialization order fiasco”。
2. 双重检查锁(Double-Checked Locking)
class Singleton {
public:
static Singleton* instance() {
Singleton* tmp = instance_;
if (!tmp) {
std::lock_guard<std::mutex> lock(mutex_);
if (!instance_)
instance_ = new Singleton();
}
return instance_;
}
static void destroy() {
std::lock_guard<std::mutex> lock(mutex_);
delete instance_;
instance_ = nullptr;
}
private:
Singleton() = default;
~Singleton() = default;
static std::atomic<Singleton*> instance_;
static std::mutex mutex_;
};
std::atomic<Singleton*> Singleton::instance_{nullptr};
std::mutex Singleton::mutex_;
-
优点
- 可以在 C++11 之前使用(需手工实现同步)。
- 只在第一次创建时加锁,后续访问几乎不受锁的影响。
-
缺点
- 需要正确使用
std::atomic,否则可能出现内存可见性问题。 - 代码复杂,易出错。
- 需要正确使用
3. std::call_once
class Singleton {
public:
static Singleton& instance() {
std::call_once(flag_, [](){ ptr_ = new Singleton(); });
return *ptr_;
}
static void destroy() {
delete ptr_;
ptr_ = nullptr;
}
private:
Singleton() = default;
~Singleton() = default;
static Singleton* ptr_;
static std::once_flag flag_;
};
Singleton* Singleton::ptr_ = nullptr;
std::once_flag Singleton::flag_;
-
优点
- 标准库直接提供线程安全的一次性初始化。
- 代码比双重检查锁更简洁、更安全。
- 适用于 C++11 及以上。
-
缺点
- 仍需要手动销毁(如果需要)。
- 对于非常频繁的访问,
call_once的内部实现会做一次状态检查,开销略高于静态局部变量。
4. 对象池方式(适用于需要多次创建和销毁的场景)
如果单例只在某些时段才需要存在,而不是一直占用内存,可以使用对象池或智能指针:
class Singleton {
public:
static std::shared_ptr <Singleton> getInstance() {
std::lock_guard<std::mutex> lock(mutex_);
if (!instance_ || instance_.expired()) {
instance_ = std::make_shared <Singleton>();
}
return instance_.lock();
}
private:
Singleton() = default;
static std::weak_ptr <Singleton> instance_;
static std::mutex mutex_;
};
std::weak_ptr <Singleton> Singleton::instance_;
std::mutex Singleton::mutex_;
-
优点
- 允许多次销毁和重新创建,适合资源需要按需释放的情况。
- 使用
shared_ptr方便管理生命周期。
-
缺点
- 需要额外的锁保护。
- 对于单例的传统定义(永不销毁)可能不合适。
选择建议
| 场景 | 推荐实现 |
|---|---|
| 需要最简洁、性能最优且使用 C++11+ | 静态局部变量 |
| 需要手动销毁实例 | std::call_once + 手动销毁 |
| 必须兼容 C++11 之前 | 双重检查锁(注意 std::atomic) |
| 需要按需销毁和重建 | 对象池 + weak_ptr |
结语
在 C++ 中实现线程安全的单例并不需要复杂的设计,现代标准已经提供了非常方便且安全的工具。选择合适的实现方式,既能保持代码的简洁性,又能满足特定的性能或生命周期需求。