多线程问题:异常处理,单例,双重检查锁定

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;

posted on   不败剑坤  阅读(32)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· .NET Core 中如何实现缓存的预热?
· 三行代码完成国际化适配,妙~啊~
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示