C++11并发与多线程总结(四) --单例模式(Singleton)下的线程安全问题

文章目录

(一) 线程概念、创建及传参
(二) 独占互斥锁–mutex,lock_guardy与其他mutex
(三) unique_lock替换lock_guardy
(四) 单例模式(Singleton)下的线程安全问题
(五) window临界区
(六) condition_variable条件变量
(七) std::async异步任务与std::future< >
(八) packaged_task< >与promise< >
(九) 原子操作atomic<>简介

(1)单例模式
  1. 单例 Singleton 是设计模式的一种,其特点是只提供唯一一个类的实例,具有全局变量的特点,在任何位置都可以通过接口获取到那个唯一实例
  2. 一般用于操作数据池,或配置属性封装成类
  3. 外部不可创建,创建后指向指针也不可释放

(2)多线程获取单例模式接口

1.若多个线程同时接口,则可能在单例初始化时多个线程同时创建了多个单例,导致内存泄漏
2.解决以上问题必须对其部分上锁,也需要考虑效率问题
3.双重判定解决(减少线程的堵塞等待)
4.std::call_once()类模板解决
5.call_once需要配合once_flag使用


  • call_once的使用
    1.首先定义一个std::once_flag flag标记,类似于锁
    2.创建初始化单例构造的成员函数
    3.在接口函数调用std::call_once()
    4.第一个参数传入flag,第二个参数传入函数名
std::once_flag g_flag;

void CreatSingleton()  //创建单例
{
	if (m_instance == nullptr)  
	{
		m_instance = new CSingleton();
		static AutoRelease autorelease;  //自动析构类
	}
}
CSingleton* CSingleton::GetInstance()
{

	std::call_once(g_flag, CreatSingleton); //多个线程进入则等待,一个线程创建,相当于std::once_flag g_flag的锁,创建后其他线程退出,后面不会再调用

	return m_instance;
}

  • 源码如下
#include <iostream>
#include<thread>
#include<mutex>

using namespace std;

class CSingleton
{
public:
	static CSingleton* GetInstance();

	void run()
	{
		m_mutexdata.lock();
		m_count++;
		m_mutexdata.unlock();
		cout << "run()执行" << m_count << endl;
	}

private:
	CSingleton(); //私有构造函数防止被外部创建
	~CSingleton(); //私有析构函数防止外部使用 delete

	class AutoRelease  //自动析构类
	{
	public:
		AutoRelease() {};
		~AutoRelease() {
			delete m_instance;
			cout << "释放单例~" << endl;
			m_instance = nullptr;
		};

	private:

	};

private:
	static CSingleton* m_instance;

	static std::mutex* m_mutex;

	int m_count = 0;
	mutex m_mutexdata; //保护data
};

CSingleton* CSingleton::m_instance = nullptr; //必须初始化
mutex* CSingleton::m_mutex = new mutex(); //必须初始化
CSingleton::CSingleton()
{
	cout << "CSingleton()创建" << endl;
}

CSingleton::~CSingleton()
{
	cout << "CSingleton()销毁" << endl;
}

CSingleton* CSingleton::GetInstance()
{
	//双检查锁,但由于内存读写reorder(优化)仍然不安全
	//由于编译成汇编时的优化,导致分配地址与构造顺序不明确(不仅C++)
	//若先分配地址而为构造,其他线程在在此时获取则会返回一个中间状态对象,造成未知错误
	if (m_instance == nullptr) //双重判定 提高效率(线程堵塞等待)
	{
		std::unique_lock<mutex> mylock(*m_mutex);//若没有这行则创建多个,测试最终data不为300

		if (m_instance == nullptr)  //若多个线程同时访问则会创建多个导致内存泄漏
		{
			m_instance = new CSingleton();
			static AutoRelease autorelease;
		}
	}

	//以上可用 void CreatSingleton() 完成new创建
	//std::call_once(g_flag, CreatSingleton); //多个线程进入则等待,一个线程创建 相当于std::once_flag g_flag的锁

	return m_instance;
}

void getSingleton()
{
	for (int i = 0; i < 100; i++)
	{
		CSingleton* p = CSingleton::GetInstance();
		p->run();
	}

}
int main()
{
	//CSingleton* p1 = CSingleton::GetInstance();
	//p1->run();

	delete p1;  //无法通过编译

	//CSingleton* p2 = CSingleton::GetInstance();
	//p2->run();

	thread mythread1(getSingleton);
	thread mythread2(getSingleton);
	thread mythread3(getSingleton);
	mythread1.join();
	mythread2.join();
	mythread3.join();
}



  • 解决双重判定reorder问题

非跨平台使用volatile关键字,C++11后使用atomic类模板解决


std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
    Singleton* tmp = m_instance.load(std::memory_order_relaxed);
    //确保内存不会被reorder优化
    std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load(std::memory_order_relaxed);
        if (tmp == nullptr) {
            tmp = new Singleton;
            //配套使用
            std::atomic_thread_fence(std::memory_order_release);//释放内存fence
            m_instance.store(tmp, std::memory_order_relaxed);
        }
    }
    return tmp;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值