在C++开发中,单例模式常被用于需要共享全局状态或资源的场景,如日志系统、配置管理、数据库连接池等。然而,当程序在多线程环境下启动时,若单例实现不当,可能导致竞态条件、重复实例化甚至崩溃。本文将以C++17为基础,详细阐述几种线程安全单例实现方法,并给出完整代码示例,帮助你在多线程环境下安全使用单例。
1. 传统懒汉式单例(非线程安全)
class LazySingleton {
public:
static LazySingleton& instance() {
if (!m_instance) {
m_instance.reset(new LazySingleton);
}
return *m_instance;
}
private:
LazySingleton() = default;
LazySingleton(const LazySingleton&) = delete;
LazySingleton& operator=(const LazySingleton&) = delete;
static std::unique_ptr <LazySingleton> m_instance;
};
std::unique_ptr <LazySingleton> LazySingleton::m_instance = nullptr;
上述实现采用懒汉式:第一次访问 instance() 时才创建对象。缺点:在多线程同时调用 instance() 时,多个线程可能进入 if (!m_instance) 判断,导致多次实例化,破坏单例性质。
2. 线程安全的懒汉式单例
2.1 带锁实现
#include <mutex>
class ThreadSafeLazySingleton {
public:
static ThreadSafeLazySingleton& instance() {
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_instance) {
m_instance.reset(new ThreadSafeLazySingleton);
}
return *m_instance;
}
private:
ThreadSafeLazySingleton() = default;
ThreadSafeLazySingleton(const ThreadSafeLazySingleton&) = delete;
ThreadSafeLazySingleton& operator=(const ThreadSafeLazySingleton&) = delete;
static std::unique_ptr <ThreadSafeLazySingleton> m_instance;
static std::mutex m_mutex;
};
std::unique_ptr <ThreadSafeLazySingleton> ThreadSafeLazySingleton::m_instance = nullptr;
std::mutex ThreadSafeLazySingleton::m_mutex;
- 优点:使用
std::lock_guard保证同一时间只有一个线程能进入初始化块,避免竞态。 - 缺点:每次获取实例都会加锁,性能略受影响。
2.2 双重检查锁(Double-Checked Locking)
class DCLSingleton {
public:
static DCLSingleton& instance() {
DCLSingleton* tmp = m_instance.load(std::memory_order_acquire);
if (!tmp) {
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_instance.load(std::memory_order_relaxed);
if (!tmp) {
tmp = new DCLSingleton;
m_instance.store(tmp, std::memory_order_release);
}
}
return *tmp;
}
private:
DCLSingleton() = default;
DCLSingleton(const DCLSingleton&) = delete;
DCLSingleton& operator=(const DCLSingleton&) = delete;
static std::atomic<DCLSingleton*> m_instance;
static std::mutex m_mutex;
};
std::atomic<DCLSingleton*> DCLSingleton::m_instance{nullptr};
std::mutex DCLSingleton::m_mutex;
- 原理:首次访问无锁检查实例是否已创建,若未创建再加锁并再次检查,避免多次创建。
- 注意:需使用
std::atomic并正确的内存序保证可见性。
3. 静态局部变量实现(C++11+)
class StaticLocalSingleton {
public:
static StaticLocalSingleton& instance() {
static StaticLocalSingleton instance; // C++11 起保证线程安全
return instance;
}
private:
StaticLocalSingleton() = default;
StaticLocalSingleton(const StaticLocalSingleton&) = delete;
StaticLocalSingleton& operator=(const StaticLocalSingleton&) = delete;
};
- 优势:语义简洁,编译器保证线程安全,性能高。
- 实现细节:C++11 通过“调用时初始化”实现
static变量的线程安全,内部使用了std::call_once机制。
4. 线程安全的 Meyer’s 单例(又称“饿汉式”)
class MeyersSingleton {
public:
static MeyersSingleton& instance() {
return *m_instance;
}
private:
MeyersSingleton() = default;
MeyersSingleton(const MeyersSingleton&) = delete;
MeyersSingleton& operator=(const MeyersSingleton&) = delete;
static std::unique_ptr <MeyersSingleton> m_instance;
};
std::unique_ptr <MeyersSingleton> MeyersSingleton::m_instance = std::make_unique<MeyersSingleton>();
- 原理:在程序启动时就构造实例,适合不需要懒加载的场景。
- 线程安全:构造时机已确定,且无并发访问风险。
5. 对比与选择
| 实现方式 | 线程安全 | 延迟加载 | 性能 | 代码复杂度 |
|---|---|---|---|---|
| 带锁懒汉式 | ✅ | ✅ | 中等 | 低 |
| DCL | ✅ | ✅ | 中等 | 中 |
| 静态局部变量 | ✅ | ✅ | 高 | 低 |
| Meyer’s 单例 | ✅ | ❌ | 高 | 低 |
| 饿汉式 | ✅ | ❌ | 高 | 低 |
- 最佳实践:若不需要延迟加载,推荐使用 静态局部变量 或 Meyers 单例。其实现最简洁且性能最优。
- 若业务需要在第一次使用时才创建对象,静态局部变量 仍是最优选择,且无需手动锁。
- 传统的
std::unique_ptr+std::mutex方案仅在兼容旧编译器或特殊需求时使用。
6. 小结
多线程环境下实现线程安全的单例并不复杂,关键在于正确使用 C++11 及之后版本提供的同步机制。最常用且推荐的做法是利用 static 局部变量,它既简洁又安全。若你对性能有极致要求或想了解更底层实现,可进一步学习 std::call_once、std::atomic 或双重检查锁。
下面给出一个完整的示例,演示在多线程环境下安全使用 StaticLocalSingleton:
#include <iostream>
#include <thread>
#include <vector>
class StaticLocalSingleton {
public:
static StaticLocalSingleton& instance() {
static StaticLocalSingleton inst;
return inst;
}
void do_something() { std::cout << "Thread " << std::this_thread::get_id() << " using singleton\n"; }
private:
StaticLocalSingleton() { std::cout << "Singleton constructed\n"; }
StaticLocalSingleton(const StaticLocalSingleton&) = delete;
StaticLocalSingleton& operator=(const StaticLocalSingleton&) = delete;
};
void worker() {
StaticLocalSingleton::instance().do_something();
}
int main() {
std::vector<std::thread> ths;
for (int i = 0; i < 10; ++i)
ths.emplace_back(worker);
for (auto& t : ths) t.join();
return 0;
}
运行结果:
Singleton constructed
Thread 140122152562176 using singleton
Thread 140122143169472 using singleton
...
可以看到,只会打印一次“Singleton constructed”,说明单例实例只创建一次,且在所有线程间安全共享。
祝你编码愉快!