在多线程环境下,单例模式常常需要保证只有一个实例被创建,并且在并发访问时不出现竞争条件。C++11 引入了对 std::call_once 和 std::once_flag 的支持,使得实现线程安全单例变得异常简洁。下面给出一个完整的示例,并对其工作原理进行详细说明。
// Singleton.h
#pragma once
#include <mutex>
#include <memory>
template <typename T>
class Singleton {
public:
// 获取单例实例
static T& instance() {
std::call_once(initFlag_, []() {
// 使用 std::unique_ptr 防止内存泄漏
instancePtr_.reset(new T());
});
return *instancePtr_;
}
// 禁止拷贝构造和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
protected:
Singleton() = default;
virtual ~Singleton() = default;
private:
static std::once_flag initFlag_;
static std::unique_ptr <T> instancePtr_;
};
// Singleton.cpp
#include "Singleton.h"
template <typename T>
std::once_flag Singleton <T>::initFlag_;
template <typename T>
std::unique_ptr <T> Singleton<T>::instancePtr_;
使用示例:
// MyService.h
#pragma once
#include <iostream>
#include <string>
class MyService {
public:
void doWork() {
std::cout << "Service doing work on thread " << std::this_thread::get_id() << std::endl;
}
};
// main.cpp
#include "Singleton.h"
#include "MyService.h"
#include <thread>
#include <vector>
int main() {
auto worker = [](){
// 访问单例
MyService& svc = Singleton <MyService>::instance();
svc.doWork();
};
// 启动多个线程并行调用
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(worker);
}
for (auto& t : threads) {
t.join();
}
return 0;
}
关键点说明
-
std::call_once+std::once_flag
std::call_once确保传入的 lambda 只会被执行一次,即使有多个线程同时调用。once_flag维护状态,内部实现使用原子操作和互斥锁,保证线程安全。 -
std::unique_ptr
用来管理单例对象的生命周期。由于instancePtr_是静态成员,它在程序结束时会被销毁,释放资源。若使用裸指针,可能会出现未定义行为。 -
模板化单例
::instance()` 获得线程安全的单例实例。
通过模板将单例模式泛化,任何类都可以通过 `Singleton -
禁用拷贝
单例不应被复制或赋值,删除拷贝构造和赋值运算符能避免不小心的错误。 -
构造函数和析构函数
保护构造函数,确保外部不能直接实例化。析构函数默认即可,因为unique_ptr会自动析构。
何时需要手工实现
如果项目使用的是 C++03 或更老版本,或者不想依赖标准库的线程支持,常见做法是使用双重检查锁(Double-Checked Locking)或静态局部变量初始化(在 C++11 之前不可在多线程环境下保证安全)。在现代 C++ 环境下,上面的方法已足够稳健且代码可读性高。
通过以上实现,你可以在任何需要单例的场景下,安全地在多线程程序中使用 `Singleton
::instance()`,无需担心实例化时的竞争问题。