在 C++ 中,单例模式是一种常见的设计模式,用于保证一个类只有一个实例,并提供全局访问点。随着多线程程序的普及,单例实现需要考虑线程安全。下面给出一个基于 C++20 的线程安全单例实现示例,并解释关键点。
核心实现
#include <mutex>
#include <memory>
#include <iostream>
class Singleton {
public:
// 删除拷贝构造和赋值运算符
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 公开静态成员函数获取单例实例
static Singleton& getInstance() {
// C++11 起的局部静态变量在首次进入函数时线程安全地初始化
// 该实现利用了此特性,确保在多线程环境下也只创建一次实例
static Singleton instance;
return instance;
}
// 示例成员函数
void doSomething() {
std::lock_guard<std::mutex> lock(mtx_);
std::cout << "Instance address: " << this << "\n";
}
private:
// 私有构造函数
Singleton() {
std::cout << "Singleton constructed\n";
}
std::mutex mtx_; // 保护内部状态的互斥锁
};
使用示例
#include <thread>
#include <vector>
void worker(int id) {
Singleton& s = Singleton::getInstance();
s.doSomething();
}
int main() {
const int threadCount = 10;
std::vector<std::thread> threads;
for (int i = 0; i < threadCount; ++i) {
threads.emplace_back(worker, i);
}
for (auto& t : threads) {
t.join();
}
return 0;
}
运行结果(示例):
Singleton constructed
Instance address: 0x7ffeefbff5a0
Instance address: 0x7ffeefbff5a0
Instance address: 0x7ffeefbff5a0
...
关键点解析
-
局部静态变量的线程安全
- C++11 起,编译器保证局部静态变量的初始化是线程安全的。
static Singleton instance;在第一次被getInstance()调用时只会被初始化一次,其他线程会等待初始化完成。
- C++11 起,编译器保证局部静态变量的初始化是线程安全的。
-
避免全局初始化顺序问题
- 传统的全局静态对象可能存在“静态初始化顺序悖论”。通过懒加载(lazy initialization),只在真正需要时才创建实例,避免了这种问题。
-
删除拷贝构造和赋值
- 为了防止复制单例实例,显式删除拷贝构造函数和赋值运算符。
-
内部状态的同步
doSomething()中使用std::mutex保护实例内部状态,确保多个线程调用该成员函数时不会产生竞争。若成员函数只是读取状态且已保证状态本身不变,可省略锁。
-
C++17 以上的
std::call_once替代方案- 也可以使用
std::call_once与std::once_flag来实现单例初始化,但static局部变量的方式更简洁、易读。
- 也可以使用
性能注意
- 对
getInstance()的调用几乎是零成本的:局部静态变量的检查在编译器层面优化为单次初始化后直接返回。 doSomething()的互斥锁会在多线程频繁调用时成为瓶颈,可根据业务需求使用读写锁或无锁设计。
总结
通过利用 C++11 及以后标准中对局部静态变量线程安全初始化的保证,配合删除拷贝构造函数、使用 std::mutex 保护内部状态,便可以实现一个简单、高效且线程安全的单例模式。该实现兼具易读性与可靠性,适合在大多数 C++ 项目中直接使用。