C++多线程环境下的单例模式实现与比较
什么是线程安全?
在多线程并行执行且存在共享数据的应用程序中,线程安全的实现通过同步机制确保各线程能够正确执行,避免数据竞争和状态不一致等问题。
线程安全保证方法
- 互斥锁机制:为共享资源添加锁,确保同一时间只有一个线程可以访问。
- 线程局部存储:为每个线程维护独立资源副本,避免共享,如C++中的
thread_local关键字。
单例模式概述
单例模式是一种设计模式,它保证在整个应用程序生命周期中,某个类只有一个实例存在,并提供全局访问点。
单例模式分类
根据实例化时机的不同,单例模式可分为:
- 延迟初始化:在首次使用时才创建实例,节省资源但需考虑线程安全问题。
- 立即初始化:在程序启动时就创建实例,直接使用,天生线程安全。
单例模式核心特征
- 私有构造与析构函数,防止外部直接创建
- 私有拷贝构造与赋值操作符,防止复制
- 公有静态获取实例方法,提供全局访问点
01 基础延迟初始化单例(非线程安全)
/////////////////// 基础延迟初始化实现 -- 非线程安全 //////////////////
#include <iostream>
#include <mutex>
#include <pthread.h>
class UniqueInstance
{
public:
// 获取单例对象
static UniqueInstance *GetInstance();
// 释放单例资源
static void ReleaseInstance();
// 显示实例地址
void ShowAddress();
private:
// 私有构造和析构函数
UniqueInstance();
~UniqueInstance();
// 私有拷贝和赋值操作
UniqueInstance(const UniqueInstance &other);
const UniqueInstance &operator=(const UniqueInstance &other);
private:
// 单例指针
static UniqueInstance *m_Instance;
};
// 初始化静态成员
UniqueInstance *UniqueInstance::m_Instance = nullptr;
UniqueInstance* UniqueInstance::GetInstance()
{
if (m_Instance == nullptr)
{
m_Instance = new (std::nothrow) UniqueInstance; // 无锁实现,多线程环境下不安全
}
return m_Instance;
}
void UniqueInstance::ReleaseInstance()
{
if (m_Instance)
{
delete m_Instance;
m_Instance = nullptr;
}
}
void UniqueInstance::ShowAddress()
{
std::cout << "实例内存地址: " << this << std::endl;
}
UniqueInstance::UniqueInstance()
{
std::cout << "构造函数执行" << std::endl;
}
UniqueInstance::~UniqueInstance()
{
std::cout << "析构函数执行" << std::endl;
}
/////////////////// 基础延迟初始化实现 -- 非线程安全 //////////////////
// 线程函数
void *ThreadFunction(void *threadid)
{
// 线程分离
pthread_detach(pthread_self());
// 获取线程ID
int tid = *((int *)threadid);
std::cout << "线程 ID: [" << tid << "] 开始执行" << std::endl;
// 获取并显示实例
UniqueInstance::GetInstance()->ShowAddress();
pthread_exit(nullptr);
}
#define THREAD_COUNT 5
int main()
{
pthread_t threads[THREAD_COUNT] = {0};
int thread_ids[THREAD_COUNT] = {0};
std::cout << "主线程: 开始创建线程..." << std::endl;
for (int i = 0; i < THREAD_COUNT; i++)
{
std::cout << "主线程: 创建线程 [" << i << "]" << std::endl;
thread_ids[i] = i;
// 创建线程
int result = pthread_create(&threads[i], nullptr, ThreadFunction, (void *)&thread_ids[i]);
if (result)
{
std::cout << "错误: 线程创建失败, 错误码: " << result << std::endl;
return -1;
}
}
// 释放单例资源
UniqueInstance::ReleaseInstance();
std::cout << "主线程: 程序结束!" << std::endl;
return 0;
}
基础延迟初始化单例测试结果:
测试显示构造函数被调用了两次,产生了两个不同的实例地址(如0x7f3c980008c0和0x7f3c900008c0),证明在多线程环境下这种实现是不安全的。
02 互斥锁保护的延迟初始化单例(线程安全)
/////////////////// 互斥锁保护的延迟初始化实现 //////////////////
class SecureSingleton
{
public:
// 获取单例对象
static SecureSingleton *&GetInstance();
// 释放单例资源
static void ReleaseInstance();
// 显示实例地址
void ShowAddress();
private:
// 私有构造和析构函数
SecureSingleton();
~SecureSingleton();
// 私有拷贝和赋值操作
SecureSingleton(const SecureSingleton &other);
const SecureSingleton &operator=(const SecureSingleton &other);
private:
// 单例指针
static SecureSingleton *m_Instance;
static std::mutex m_Mutex;
};
// 初始化静态成员
SecureSingleton *SecureSingleton::m_Instance = nullptr;
std::mutex SecureSingleton::m_Mutex;
SecureSingleton *&SecureSingleton::GetInstance()
{
// 双重检查锁定模式
if (m_Instance == nullptr)
{
std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
if (m_Instance == nullptr)
{
m_Instance = new (std::nothrow) SecureSingleton;
}
}
return m_Instance;
}
void SecureSingleton::ReleaseInstance()
{
std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
if (m_Instance)
{
delete m_Instance;
m_Instance = nullptr;
}
}
void SecureSingleton::ShowAddress()
{
std::cout << "实例内存地址: " << this << std::endl;
}
SecureSingleton::SecureSingleton()
{
std::cout << "构造函数执行" << std::endl;
}
SecureSingleton::~SecureSingleton()
{
std::cout << "析构函数执行" << std::endl;
}
/////////////////// 互斥锁保护的延迟初始化实现 //////////////////
互斥锁保护的延迟初始化单例测试结果:
测试显示只创建了一个实例,地址为0x7f28b00008c0,证明互斥锁保护下的延迟初始化是线程安全的。
03 静态局部变量的延迟初始化单例(C++11线程安全)
/////////////////// 静态局部变量的延迟初始化实现 //////////////////
class LazySingleton
{
public:
// 获取单例对象
static LazySingleton &GetInstance();
// 显示实例地址
void ShowAddress();
private:
// 私有构造函数
LazySingleton();
// 私有析构函数
~LazySingleton();
// 私有拷贝构造
LazySingleton(const LazySingleton &other);
// 私有赋值操作
const LazySingleton &operator=(const LazySingleton &other);
};
LazySingleton &LazySingleton::GetInstance()
{
// 利用局部静态变量特性实现单例
static LazySingleton instance;
return instance;
}
void LazySingleton::ShowAddress()
{
std::cout << "实例内存地址: " << this << std::endl;
}
LazySingleton::LazySingleton()
{
std::cout << "构造函数执行" << std::endl;
}
LazySingleton::~LazySingleton()
{
std::cout << "析构函数执行" << std::endl;
}
/////////////////// 静态局部变量的延迟初始化实现 //////////////////
静态局部变量的延迟初始化单例测试结果:
使用C++11标准(-std=c++11)编译时,静态局部变量的实现是线程安全的,只创建一次实例(地址如0x6016e8)。这种方法代码简洁,推荐使用。
04 立即初始化单例(天生线程安全)
////////////////////////// 立即初始化实现 /////////////////////
class EagerSingleton
{
public:
// 获取单例
static EagerSingleton* GetInstance();
// 释放单例资源
static void ReleaseInstance();
// 显示实例地址
void ShowAddress();
private:
// 私有构造和析构函数
EagerSingleton();
~EagerSingleton();
// 私有拷贝和赋值操作
EagerSingleton(const EagerSingleton &other);
const EagerSingleton &operator=(const EagerSingleton &other);
private:
// 单例指针,程序启动时初始化
static EagerSingleton *m_Instance;
};
// 程序启动时即创建实例,天生线程安全
EagerSingleton* EagerSingleton::m_Instance = new (std::nothrow) EagerSingleton;
EagerSingleton* EagerSingleton::GetInstance()
{
return m_Instance;
}
void EagerSingleton::ReleaseInstance()
{
if (m_Instance)
{
delete m_Instance;
m_Instance = nullptr;
}
}
void EagerSingleton::ShowAddress()
{
std::cout << "实例内存地址: " << this << std::endl;
}
EagerSingleton::EagerSingleton()
{
std::cout << "构造函数执行" << std::endl;
}
EagerSingleton::~EagerSingleton()
{
std::cout << "析构函数执行" << std::endl;
}
////////////////////////// 立即初始化实现 /////////////////////
立即初始化单例测试结果:
立即初始化方式在程序启动时即创建实例,因此天生就是线程安全的。
实现方案比较与选择
- 延迟初始化:以时间换空间,适用于资源访问较少的场景。推荐使用静态局部变量的C++11实现,代码简洁且线程安全。
- 立即初始化:以空间换时间,适用于资源访问频繁或高并发环境,启动时创建实例确保线程安全。