单例模式(Singleton)是一种常用的设计模式,确保一个类只有一个实例,并提供全局访问点。在多线程环境中,必须保证实例化过程是线程安全的,否则可能出现多个实例被创建的情况。下面我们将介绍几种在C++中实现线程安全单例的常见方法,并对它们的优缺点进行分析。
1. Meyer’s Singleton(函数内部静态变量)
class Singleton {
public:
static Singleton& instance() {
static Singleton instance; // C++11起,局部静态变量初始化是线程安全的
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
~Singleton() = default;
};
优点
- 简洁明了,几乎不需要任何额外代码。
- 通过
delete删除拷贝构造函数和赋值运算符,避免不小心复制。 - C++11 标准保证局部静态变量的初始化是线程安全的。
缺点
- 只能在程序运行期间存在,无法在程序结束前显式销毁。
- 对于多线程的首次调用,可能会有轻微的性能开销(锁的开销)。
2. 双重检查锁(Double-Check Locking)
#include <atomic>
#include <mutex>
class Singleton {
public:
static Singleton* instance() {
Singleton* tmp = instance_.load(std::memory_order_acquire);
if (!tmp) {
std::lock_guard<std::mutex> lock(mutex_);
tmp = instance_.load(std::memory_order_relaxed);
if (!tmp) {
tmp = new Singleton;
instance_.store(tmp, std::memory_order_release);
}
}
return tmp;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
~Singleton() = default;
static std::atomic<Singleton*> instance_;
static std::mutex mutex_;
};
std::atomic<Singleton*> Singleton::instance_{nullptr};
std::mutex Singleton::mutex_;
优点
- 延迟实例化(lazy initialization)与线程安全兼顾。
- 在第一次实例化后,后续获取实例时不需要加锁,性能更高。
缺点
- 代码较为复杂,易出错。
- 需要手动管理实例生命周期(需要显式删除)。
3. 静态成员指针与 std::call_once
#include <mutex>
class Singleton {
public:
static Singleton& instance() {
std::call_once(initFlag_, []() { instance_ = new Singleton; });
return *instance_;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
~Singleton() = default;
static Singleton* instance_;
static std::once_flag initFlag_;
};
Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;
优点
- 利用标准库提供的
std::call_once与std::once_flag实现一次性初始化,代码相对简洁。 - 与双重检查锁相比更安全,避免了因指针未对齐导致的竞态。
缺点
- 需要手动管理实例指针,可能出现内存泄漏。
- 同样无法显式销毁实例。
4. 经典的“静态成员变量 + 互斥量”实现
class Singleton {
public:
static Singleton& instance() {
std::lock_guard<std::mutex> lock(mutex_);
if (!instance_) {
instance_ = new Singleton;
}
return *instance_;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
~Singleton() = default;
static Singleton* instance_;
static std::mutex mutex_;
};
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;
优点
- 简单直观,适用于 C++11 之前的标准(如 C++03)。
- 通过
std::mutex保证线程安全。
缺点
- 每次获取实例时都要加锁,导致性能下降。
- 需要手动销毁,且在多线程环境下需要注意析构时机。
5. 对比与选择
| 方法 | 线程安全保证 | 初始化时机 | 代码复杂度 | 生命周期管理 | 适用场景 |
|---|---|---|---|---|---|
| Meyer’s Singleton | 通过局部静态实现 | 程序启动时首次调用 | 简单 | 隐式销毁 | C++11+ |
| 双重检查锁 | 原子 + mutex | 延迟 | 高 | 手动 | 高并发后期访问 |
| call_once | std::call_once | 延迟 | 中等 | 手动 | 需要一次性初始化 |
| 静态指针 + mutex | 互斥锁 | 延迟 | 简单 | 手动 | 兼容旧标准 |
- 如果你使用 C++11 及以后版本,推荐使用 Meyer’s Singleton。它最简洁,性能最优,且线程安全保证可靠。
- 如果你在 C++11 之前,或者想在运行时动态决定是否实例化,call_once 或 双重检查锁 是较好的选择。
- 如果你需要在程序结束前显式销毁实例,可以结合
std::unique_ptr与自定义std::atexit函数来完成。
6. 参考代码:完整可运行示例
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include <mutex>
#include <memory>
#include <chrono>
class ThreadSafeSingleton {
public:
static ThreadSafeSingleton& getInstance() {
static ThreadSafeSingleton instance; // C++11 线程安全初始化
return instance;
}
void doWork(int id) {
std::lock_guard<std::mutex> lock(mutex_);
std::cout << "Thread " << id << " is using singleton instance at " << this << std::endl;
}
private:
ThreadSafeSingleton() {
std::cout << "Singleton constructed at " << this << std::endl;
}
~ThreadSafeSingleton() = default;
ThreadSafeSingleton(const ThreadSafeSingleton&) = delete;
ThreadSafeSingleton& operator=(const ThreadSafeSingleton&) = delete;
std::mutex mutex_;
};
void worker(int id) {
auto& singleton = ThreadSafeSingleton::getInstance();
singleton.doWork(id);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
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 at 0x7ffc1d0b2d30
Thread 0 is using singleton instance at 0x7ffc1d0b2d30
Thread 1 is using singleton instance at 0x7ffc1d0b2d30
...
7. 小结
- 单例模式在 C++ 中实现不难,但在多线程环境中需要特别注意初始化的线程安全性。
- C++11 标准提供了最简洁的
Meyer's Singleton方案,利用局部静态变量实现线程安全的延迟初始化。 - 其它方案(双重检查锁、
std::call_once、手动锁)适用于对初始化时机、生命周期控制或兼容旧标准的特殊需求。 - 在实际项目中,应根据需求、编译器支持程度、性能考虑以及代码维护成本综合评估,选择最合适的实现方式。