在多线程环境下实现单例模式,需要确保在并发访问时只创建一个实例,同时避免竞态条件和性能瓶颈。下面详细介绍几种常见实现方式及其优缺点,并给出完整可编译的示例代码。
1. 懒汉式(线程不安全)
最直观的做法是使用静态指针并在第一次访问时延迟初始化。
class Singleton {
private:
Singleton() {}
public:
static Singleton* getInstance() {
if (!instance) {
instance = new Singleton();
}
return instance;
}
private:
static Singleton* instance;
};
Singleton* Singleton::instance = nullptr;
缺点:在多线程情况下,两个线程可能同时进入 if (!instance) 并各自创建实例,导致资源浪费甚至破坏单例。
2. 懒汉式 + 双重检查锁(DCL)
使用互斥锁配合双重检查,减少锁开销。
class Singleton {
private:
Singleton() {}
static std::atomic<Singleton*> instance;
static std::mutex mtx;
public:
static Singleton* getInstance() {
Singleton* tmp = instance.load(std::memory_order_acquire);
if (!tmp) {
std::lock_guard<std::mutex> lock(mtx);
tmp = instance.load(std::memory_order_relaxed);
if (!tmp) {
tmp = new Singleton();
instance.store(tmp, std::memory_order_release);
}
}
return tmp;
}
};
std::atomic<Singleton*> Singleton::instance{nullptr};
std::mutex Singleton::mtx;
优点:只有第一次创建时才加锁,后续访问几乎不受锁影响。
缺点:实现复杂,容易出现细节错误(如指令重排导致的可见性问题)。
3. 静态局部变量(C++11 线程安全)
C++11 引入了线程安全的局部静态变量初始化。
class Singleton {
private:
Singleton() {}
public:
static Singleton& getInstance() {
static Singleton instance; // 第一次调用时初始化,线程安全
return instance;
}
// 禁止拷贝和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
优点:代码简洁、无锁,线程安全且延迟初始化。
缺点:无法在单例销毁时执行自定义逻辑(除非使用 std::atexit)。
4. Meyer’s Singleton(单例实现者)
与静态局部变量相同,只是命名更符合单例设计模式。
class Singleton {
public:
static Singleton& Instance() {
static Singleton instance;
return instance;
}
private:
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
5. 基于 std::call_once 的实现
使用 std::once_flag 与 std::call_once 可以在多线程环境中确保仅执行一次初始化逻辑。
class Singleton {
private:
Singleton() {}
static std::unique_ptr <Singleton> instance;
static std::once_flag flag;
public:
static Singleton& getInstance() {
std::call_once(flag, [](){ instance.reset(new Singleton()); });
return *instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
std::unique_ptr <Singleton> Singleton::instance = nullptr;
std::once_flag Singleton::flag;
优点:实现简单且线程安全,适用于需要动态分配的单例。
何时使用哪种实现?
| 场景 | 推荐实现 | 说明 |
|---|---|---|
| 需要延迟初始化、且 C++11+ | 静态局部变量 / Meyer’s | 最简洁、无锁、线程安全 |
| 需要自定义销毁顺序 | std::call_once + unique_ptr |
可在 unique_ptr 析构时控制 |
| 对性能极致敏感,且已知单例在多线程下仅创建一次 | 双重检查锁(DCL) | 适合极端性能场景,但实现繁琐 |
| 旧编译器不支持 C++11 | 双重检查锁(DCL)或手动锁 | 兼容旧环境,但需注意指令重排 |
代码完整示例(C++17)
#include <iostream>
#include <mutex>
#include <memory>
class Logger {
public:
static Logger& Instance() {
static Logger instance; // C++11+ 线程安全
return instance;
}
void log(const std::string& msg) {
std::lock_guard<std::mutex> lock(mtx_);
std::cout << msg << std::endl;
}
private:
Logger() {}
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
std::mutex mtx_;
};
int main() {
// 多线程测试
std::thread t1([](){ Logger::Instance().log("Thread 1"); });
std::thread t2([](){ Logger::Instance().log("Thread 2"); });
t1.join(); t2.join();
return 0;
}
总结:在 C++ 中实现线程安全单例最推荐的方式是利用 C++11 引入的局部静态变量,代码简洁且性能优秀。若需要更细粒度的控制(如自定义销毁顺序),
std::call_once与std::unique_ptr也是非常稳妥的方案。始终记得禁用拷贝构造和赋值操作,避免出现多实例。