在多线程环境下,单例模式需要保证只有一个实例被创建,并且在所有线程之间共享同一个实例。下面我们从几种常见实现方式入手,详细阐述它们的工作原理、优缺点以及最佳实践。
1. 饿汉式(Eager Initialization)
class Singleton {
public:
static Singleton& instance() {
static Singleton instance; // 在第一次调用时创建
return instance;
}
private:
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
优点
- 线程安全:
static局部变量在 C++11 之后的实现是线程安全的。编译器会在第一次访问instance()时使用内部锁保证单例初始化仅执行一次。 - 代码简洁:不需要手动管理锁或使用
std::call_once。
缺点
- 饿汉式:如果实例创建开销大且程序可能不使用单例,仍会在程序启动时就实例化,造成资源浪费。
- 缺乏延迟:无法控制实例何时创建。
2. 懒汉式 + std::call_once
class Singleton {
public:
static Singleton& instance() {
std::call_once(flag_, []() { instance_ = new Singleton(); });
return *instance_;
}
~Singleton() { delete instance_; }
private:
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static std::once_flag flag_;
static Singleton* instance_;
};
std::once_flag Singleton::flag_;
Singleton* Singleton::instance_ = nullptr;
优点
- 延迟初始化:只有真正调用
instance()时才创建对象。 - 线程安全:
std::call_once在多线程环境下只会执行一次回调,保证单例唯一。
缺点
- 手动内存管理:需要显式删除对象,否则会导致内存泄漏(在
main()结束前删除或使用std::unique_ptr)。 - 略显繁琐:需要维护
once_flag与指针。
3. 双重检查锁(Double-Check Locking)— 不推荐
class Singleton {
public:
static Singleton* instance() {
if (!instance_) {
std::lock_guard<std::mutex> lock(mutex_);
if (!instance_) {
instance_ = new Singleton();
}
}
return instance_;
}
private:
Singleton() {}
static std::mutex mutex_;
static Singleton* instance_;
};
说明:在 C++11 之前,编译器对内存模型的支持不完善,导致双重检查锁可能出现可见性问题。C++11 之后已经可以安全实现,但仍不如
std::call_once简洁。
4. 使用 C++17 的 inline static
class Singleton {
public:
static Singleton& instance() {
static inline Singleton instance;
return instance;
}
private:
Singleton() {}
};
inline static 让成员变量可以在头文件中定义,避免多重定义错误。此实现与饿汉式相同,但更现代。
5. 关键点总结
| 方法 | 线程安全 | 延迟 | 代码复杂度 | 适用场景 |
|---|---|---|---|---|
| 饿汉式 | ✅ | ❌ | 简单 | 资源小,应用必需 |
| std::call_once | ✅ | ✅ | 中等 | 需要延迟且资源较大 |
| 双重检查锁 | ✅ | ✅ | 高 | 旧代码兼容,慎用 |
| inline static | ✅ | ❌ | 简单 | C++17 及以上 |
6. 实际案例:线程池单例
class ThreadPool {
public:
static ThreadPool& getInstance(std::size_t threads = std::thread::hardware_concurrency()) {
std::call_once(flag_, [threads](){ instance_ = new ThreadPool(threads); });
return *instance_;
}
void submit(std::function<void()> task) {
{
std::unique_lock<std::mutex> lock(queue_mutex_);
tasks_.emplace(std::move(task));
}
cond_.notify_one();
}
private:
ThreadPool(std::size_t threads) : stop_(false) {
for (std::size_t i = 0; i < threads; ++i)
workers_.emplace_back([this](){ this->worker(); });
}
void worker() {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queue_mutex_);
cond_.wait(lock, [this](){ return stop_ || !tasks_.empty(); });
if (stop_ && tasks_.empty()) return;
task = std::move(tasks_.front());
tasks_.pop();
}
task();
}
}
~ThreadPool() { stop(); }
void stop() {
{
std::lock_guard<std::mutex> lock(queue_mutex_);
stop_ = true;
}
cond_.notify_all();
for (auto& w : workers_) w.join();
}
std::vector<std::thread> workers_;
std::queue<std::function<void()>> tasks_;
std::mutex queue_mutex_;
std::condition_variable cond_;
bool stop_;
static std::once_flag flag_;
static ThreadPool* instance_;
};
std::once_flag ThreadPool::flag_;
ThreadPool* ThreadPool::instance_ = nullptr;
此实现演示了 std::call_once 与单例结合的完整线程池示例,体现了延迟初始化与多线程安全的实际应用。
7. 小结
- C++11 以后,
std::call_once和局部静态变量是实现线程安全单例最推荐的方式。 - 饿汉式最为简洁,但缺乏延迟;懒汉式结合
call_once兼顾延迟与安全。 - 双重检查锁在现代 C++ 中不再必要,除非你必须兼容旧标准。
- 对于 C++17,
inline static让单例实现更简洁。
遵循这些原则,你可以在任何 C++ 项目中安全、可靠地使用单例模式。