多线程问题:异常处理,单例,双重检查锁定
1)多线程异常处理
多线程中如何捕获抛出异常到主线程
a)catch中使用std::current_exception();获得正在处理的异常
b)通过引用类型std::exception_ptr& _ex_ptr 传出
c)std::rethrow_exception(ex_ptr);重新抛出异常
using namespace std;
try {
std::exception_ptr ex_ptr;
thread th([](std::exception_ptr& _ex_ptr) {
try {
int sum = 0;
for (int i = 0; i <= 9; i++)
{
sum += i;
}
throw(std::runtime_error("error level:1"));
}
catch (std::exception& _ex)
{
_ex_ptr = std::current_exception();
//获得当前正在处理的异常
}
}, ref(ex_ptr));
th.join();
if(ex_ptr)
std::rethrow_exception(ex_ptr);
//重新抛出异常
}
catch (std::exception& ex)
{
cout << "exception:" << ex.what() << endl;
}
2)多线程中(单例类)类对象的初始化问题
a)双重检查锁定:
减少同步开销:只有在实例尚未创建时才需要获取锁,减少了锁的竞争,提高了性能。
大多数情况下,实例已经创建,第二次检查和锁的获取可以避免。
b) C++11 标准保证了静态局部变量的线程安全初始化,可以简化单例模式的实现。
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // 线程安全的静态局部变量
return instance;
}
private:
Singleton() {} // 私有构造函数,确保单例模式
~Singleton() {} // 私有析构函数,确保单例模式
Singleton(const Singleton&) = delete; // 禁用拷贝构造函数
Singleton& operator=(const Singleton&) = delete; // 禁用赋值操作符
};
c) call once;
C++11 提出了call_once函数,我们可以配合一个局部的静态变量once_flag实现线程安全的初始化。 多线程调用call_once函数时,会判断once_flag是否被初始化,
如没被初始化则进入初始化流程,调用我们提供的初始化函数。 但是同一时刻只有一个线程能进入这个初始化函数
class Bolala {
public:
static Bolala* get_Instance()
{
static std::once_flag s_flag;
//std::call_once(once_flag,function)
std::call_once(s_flag, []() {
bola_ptr = new Bolala();
std::cout << "once init" << std::endl;
});
return bola_ptr;
}
private:
static Bolala* bola_ptr;
};
Bolala* Bolala::bola_ptr = nullptr;
为什么 s_flag 不需要显式初始化?而bola_ptr 需要
零初始化:std::once_flag 是一个标准库提供的类型,它的默认构造函数会将其内部状态初始化为一个“未调用”状态。
这意味着 std::once_flag 在声明时已经自动初始化为一个有效的状态。
局部静态变量:s_flag 是一个局部静态变量,C++ 标准保证局部静态变量在第一次被使用前会被自动初始化。因此,你不需要显式初始化 s_flag。
C++ 标准规定,静态成员变量必须在类外部进行初始化。因此,你需要在类外部显式初始化 bola_ptr
局部静态变量:s_flag 是一个局部静态变量,C++ 标准保证局部静态变量在第一次被使用前会被自动初始化。因此,你不需要显式初始化 s_flag。
3)单例的双重检查锁定实现存在的问题
//利用智能指针解决释放问题
class SingleAuto
{
private:
SingleAuto()
{
}
SingleAuto(const SingleAuto&) = delete;
SingleAuto& operator=(const SingleAuto&) = delete;
public:
~SingleAuto()
{
std::cout << "single auto delete success " << std::endl;
}
static std::shared_ptr<SingleAuto> GetInst()
{
// 1 处
if (single != nullptr)
{
return single;
}
// 2 处
s_mutex.lock();
// 3 处
if (single != nullptr)
{
s_mutex.unlock();
return single;
}
// 4处
single = std::shared_ptr<SingleAuto>(new SingleAuto);
s_mutex.unlock();
return single;
}
private:
static std::shared_ptr<SingleAuto> single;
static std::mutex s_mutex;
};
这里的双重检查锁定在某些情况下会导致奔溃:
假设有 多个线程使用单例,首个单例运行至4处,由于new一个对象再赋值给变量时会存在多个指令顺序
如果顺序是:new一个指针->返回指针->在这个地址构造单例类
此时刚返回指针(尚未构造),另一个线程读取这个地址,程序奔溃
解决:
使用atomic_bool 判断是否已初始化,load时使用(acquire)内存模型确保内存顺序
//利用智能指针解决释放问题
class SingleMemoryModel
{
private:
SingleMemoryModel()
{
}
SingleMemoryModel(const SingleMemoryModel&) = delete;
SingleMemoryModel& operator=(const SingleMemoryModel&) = delete;
public:
~SingleMemoryModel()
{
std::cout << "single auto delete success " << std::endl;
}
static std::shared_ptr<SingleMemoryModel> GetInst()
{
// 1 处
if (_b_init.load(std::memory_order_acquire))
{
return single;
}
// 2 处
s_mutex.lock();
// 3 处
//此时已经在锁保护范围内,不需要更强的内存顺序保证,所以用最弱的内存顺序
if (_b_init.load(std::memory_order_relaxed))
{
s_mutex.unlock();
return single;
}
// 4处
single = std::shared_ptr<SingleMemoryModel>(new SingleMemoryModel);
//构造完成后设置状态为true
_b_init.store(true, std::memory_order_release);
s_mutex.unlock();
return single;
}
private:
static std::shared_ptr<SingleMemoryModel> single;
static std::mutex s_mutex;
static std::atomic<bool> _b_init ;
};
std::shared_ptr<SingleMemoryModel> SingleMemoryModel::single = nullptr;
std::mutex SingleMemoryModel::s_mutex;
std::atomic<bool> SingleMemoryModel::_b_init = false;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· .NET Core 中如何实现缓存的预热?
· 三行代码完成国际化适配,妙~啊~
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?