在多线程环境下,单例模式的实现需要保证以下两点:
- 全局唯一性:无论程序如何调用,所有线程都能获得同一实例。
- 线程安全:实例化过程中不被并发破坏,避免出现“半初始化”的情况。
下面给出一种简洁、现代化且符合C++11及以后标准的实现方案,并对关键点进行逐行说明。
1. 利用局部静态变量的特性
C++11 起,函数内部的局部静态变量在第一次访问时会被安全地初始化(线程安全)。这正好满足单例的“只创建一次”需求。
class Singleton {
public:
// 公开的获取实例接口
static Singleton& getInstance() {
// 这里的static保证仅在第一次调用时初始化一次
static Singleton instance;
return instance;
}
// 禁止拷贝和移动构造/赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
Singleton(Singleton&&) = delete;
Singleton& operator=(Singleton&&) = delete;
// 示例功能
void doSomething() {
std::cout << "Singleton instance address: " << this << std::endl;
}
private:
// 构造函数私有化
Singleton() {
std::cout << "Singleton constructed." << std::endl;
}
~Singleton() = default;
};
关键点说明
| 行号 | 说明 |
|---|---|
static Singleton instance; |
第一次调用 getInstance() 时才会构造 instance,后续调用直接返回已存在对象。C++11 标准保证了此初始化是互斥的,所有线程会等待。 |
Singleton(const Singleton&) = delete; 等 |
防止外部代码通过拷贝或移动得到多份实例,保持全局唯一性。 |
private: Singleton(); |
构造函数私有化,外部无法直接实例化。 |
2. 延迟加载与销毁
- 延迟加载:上述实现天然支持懒加载,只有真正需要实例时才创建,节省资源。
- 销毁顺序:局部静态变量会在程序结束时按逆序析构,确保在程序退出前所有资源被释放。
3. 使用示例
#include <thread>
#include <iostream>
void threadFunc() {
Singleton& s = Singleton::getInstance();
s.doSomething();
}
int main() {
std::thread t1(threadFunc);
std::thread t2(threadFunc);
t1.join();
t2.join();
return 0;
}
运行结果示例:
Singleton constructed.
Singleton instance address: 0x55e8b2d3e040
Singleton instance address: 0x55e8b2d3e040
可以看到,构造函数只被调用一次,所有线程获得的地址相同,说明实例唯一且线程安全。
4. 进阶:自定义销毁顺序
如果需要在程序结束前手动销毁单例(例如,先关闭网络连接再关闭日志文件),可以采用 std::atexit 或 std::shared_ptr 配合自定义 deleter:
class Singleton {
public:
static Singleton& getInstance() {
static Singleton* instance = new Singleton();
static std::atexit(destroyInstance);
return *instance;
}
// 其它成员同上...
private:
static void destroyInstance() {
delete instance;
instance = nullptr;
}
};
此方案在 atexit 时释放实例,保证资源按用户指定顺序析构。
5. 总结
- 利用 C++11 局部静态变量的线程安全初始化可以实现极简、可靠的单例。
- 禁止拷贝/移动构造、赋值确保唯一性。
- 如需特殊销毁顺序,可结合
atexit或智能指针实现。
这套实现已在多线程项目中广泛使用,兼容性强且易于维护。