IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> Th7:单例设计模式共享数据问题的分析和解决,call_once -> 正文阅读

[C++知识库]Th7:单例设计模式共享数据问题的分析和解决,call_once

《1》设计模式大概谈

????????所谓“设计模式”,指的是代码的一些写法(这些写法与常规的写法不一样)。它会使得程序变得灵活,维护起来可能方便。但,用设计模式理念写出来的代码很晦涩,别人接管、阅读代码都会很痛苦,需要下很大功夫才能搞clear。
????????设计模式最先是老外应付特别大的项目时,把项目的开发经验、模块划分经验,总结整理而成的。0几年设计模式在中国刚开始火时,程序员们总喜欢拿一个设计模式往项目代码上套,导致一个小小的项目总要加几个设计模式,这就本末倒置了。
? ? ? ? 但不可否认的是,设计模式有其独特的优点,作为程序员,一定要活学活用,不要深陷其中,生搬硬套!

《2》单例设计模式(常用)

????????在整个项目中,有某个或者某些特殊的类,只能创建一个属于该类的对象。这即为单例设计模式。
????????单例类:只能生成该类的一个对象。

demo_codes:

#include<iostream>
using namespace std;
//单例类例子codes
class MyCLS {
private:
	MyCLS(){}//私有化类的构造函数时,类外部就不可以用className ObjName的方式来创建类的对象了。
private:
	static MyCLS* m_instance;//静态成员变量必须是类内声明,类外初始化
public:
	static MyCLS* getInstance() {
		if (m_instance == nullptr) {
			m_instance = new MyCLS();
			static CGarHuishou cl;
            //静态局部变量 <==> 全局变量(在整一个程序运行结束时,其作用域才end)
            //so才可以用另外一个类来帮助我delete上述在heap区开辟内存空间的m_instance指针
		}
		//shared_ptr<MyCLS>(make_shared())
		return m_instance;
	}
	void func() {
		static int nums = 0;
		nums++;
		cout << "test codes " <<nums<< endl;
	}

	class CGarHuishou {//类中嵌套另一个类,这个类专门用于析构单例类中的new的对象所占据的内存空间!
	public:
		~CGarHuishou() {
			if (MyCLS::m_instance) {//非空 再delete,若本来就是空则不要重复delete
				delete MyCLS::m_instance;
				MyCLS::m_instance = nullptr;
				cout << "~CGarHuishou()执行了!" << endl;
			}
		}
	};
};
//静态成员变量必须是类内声明,类外初始化
MyCLS* MyCLS::m_instance = nullptr;
int main(void) {
	MyCLS* weiyiObj = MyCLS::getInstance();//因为是static成员,所以要用::符号来使用该函数!
	MyCLS* weiyiObj2 = MyCLS::getInstance();//因为是static成员,所以要用::符号来使用该函数!
	weiyiObj->func();
	weiyiObj2->func();
	MyCLS::getInstance()->func();
    if (weiyiObj == weiyiObj2)cout << "weiyiObj == weiyiObj2" << endl;
	else cout<< "weiyiObj != weiyiObj2" << endl;
    //从运行结果来看,这2个指针都指向同一块内存,说明只可以产生一个该类的对象!
    //也即,实际上,都只是产生了一块内存而已!
	return 0;
}

运行结果:

《3》单例设计模式共享数据问题分析、解决

????????一般,我们都是在主线程中(main函数内)创建单例类的对象,这一不会引起任何问题。

????????但是,现在我们面临着另一个问题:需要在自己创建的线程中(非主线程中)来创建单例类的对象(比如上述所说的MyCLS类),这种线程可能不止一个。此时,我们可能面临多个GetInstance()成员函数需要互斥的情况。
????????可以在加锁前判断m_instance是否为空,否则每次调用Singleton::getInstance()都要加锁,十分影响效率。

????????因为每次调用MyCLS::getInstance()都要加锁,十分影响效率。因此,在加锁前再次判断m_instance是否为空(这即为双重锁定/双重检查)。

demo_codes:(please仔细看注释,and you will get what I say!)

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
std::mutex my_mutex;
//单例类例子codes
class MyCLS {
private:
	MyCLS(){}//私有化类的构造函数时,类外部就不可以用className ObjName的方式来创建类的对象了。
private:
	static MyCLS* m_instance;//静态成员变量必须是类内声明,类外初始化
public:
	//首先你得承认一个事实是:
	//如果if (m_instance != nullptr) 条件成立,一定代表m_instance一定被new过了
	//如果if (m_instance == nullptr) 条件成立,并不代表m_instance一定没被new过了

	//下面这个创建对象指针的函数getInstance()就算多个线程一起共享的代码数据!
	static MyCLS* getInstance() {
??? //因为每次调用MyCLS::getInstance()都要加锁,十分影响效率。
    //因此,在加锁前再次判断m_instance是否为空(这即为双重锁定/双重检查)。
		if (m_instance == nullptr) {//这就叫做是双重检查or双重锁定
			std::lock_guard<std::mutex> sbguard(my_mutex);//自动加锁
			if (m_instance == nullptr) {
				m_instance = new MyCLS();
				static CGarHuishou cl;//静态成员
			}
		}
		return m_instance;
	}
	void func() {
		static int nums = 0;
		nums++;
		cout << "test codes " <<nums<< endl;
	}

	class CGarHuishou {//类中嵌套另一个类,这个类专门用于析构单例类中的new的对象所占据的内存空间!
	public:
		~CGarHuishou() {
			if (MyCLS::m_instance) {//非空 再delete
				delete MyCLS::m_instance;
				MyCLS::m_instance = nullptr;
				cout << "~CGarHuishou()执行了!" << endl;
			}
		}
	};
};
//静态成员变量必须是类内声明,类外初始化
MyCLS* MyCLS::m_instance = nullptr;
//线程入口函数
void mythread() {
	cout << "我的线程开始执行了!" << endl;
	MyCLS* p_a = MyCLS::getInstance();//这里很可能就会出问题了!
	cout << "我的线程执行完毕了!" << endl;
	return;
}
int main(void) {
	//创建了2个线程,虽然这2个线程都分别走的是同一条通路,
	//但是,毕竟是2个线程,彼此互不相关
	//这时候问题就来了,当mytobj1这个线程1执行到MyCLS* p_a = MyCLS::getInstance();时
	//刚刚准备调用MyCLS类的getInstance函数中的m_instance = new MyCLS();这行代码时,还没运行呢
	//操作系统就切换到mytobj2这个线程2中去了,比如此时线程2刚好new了MyCLS对象后,操作系统又切换回
	//mytobj1这个线程1中去,那么此时线程1又会new一个MyCLS对象!那么此时就会有2个MyCLS对象的出现
	//这就出现问题了,因为单例类中只允许创建一个对象!

    //此时,就可以用双重锁定的way去deal该问题!
	thread mytobj1(mythread);
	thread mytobj2(mythread);
	mytobj1.join();
	mytobj2.join();
	return 0;
}

《4》std::call_once()

????????std::call_once()函数模板:保证一个函数只被调用一次(C++11引入的函数模板)

????????格式

std:once_flag flagObjName;
std::call_once(flagObjName,funcName);

????????std::call_once()保证一个函数只被调用一次的原理

????????call_once()需要与一个标记配合使用,这个标记是:std::once_flag(它是一个结构体)。通过std::once_flag这个标记,call_once()就可以决定对应的函数名为funcName的函数是否能执行1次了。当once_flag设置为“未调用”状态时,call_once()很自然地允许别的代码调用该funcName函数一次,然后将once_flag设置为“已调用”状态。那么此后无论有多少次对于该funcName函数的调用,call_once函数模板都不会允许!

? ? ? ? 解释

????????之所以能保证该funcName函数只被调用一次。本质上是因为std::call_once这个函数模板具有mutex互斥量的功能。因此可以实现出:当多个线程同时执行时,一个线程会等待另一个线程先执行的效果(和mutex互斥量一样的效果)。

demo_codes:(please仔细看注释,and you will get what I say!)

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
std::mutex my_mutex;

std::once_flag  g_flag;//这是一个系统定义的标记
//单例类例子codes
class MyCLS {
public:
	static void CreateInstance() {//只允许该函数被调用一次!s
		if (m_instance == nullptr) {
			m_instance = new MyCLS();
			static CGarHuishou cl;//静态成员
		}
	}
private:
	MyCLS(){}//私有化类的构造函数时,类外部就不可以用className ObjName的方式来创建类的对象了。
private:
	static MyCLS* m_instance;//静态成员变量必须是类内声明,类外初始化
public:
	//下面这个创建对象指针的函数getInstance()就算多个线程一起共享的代码数据!
	static MyCLS* getInstance() {
		//if (m_instance == nullptr) {//这就叫做是双重检查or双重锁定
		//	std::lock_guard<std::mutex> sbguard(my_mutex);//自动加锁
		//	if (m_instance == nullptr) {
		//		m_instance = new MyCLS();
		//		static CGarHuishou cl;//静态成员
		//	}
		//}

		std::call_once(g_flag, CreateInstance);
		cout << "call_once执行完毕!" << endl;
		//当2个线程都执行到此处时,其中一个线程要等待另一个线程执行完CreateInstance()函数才能够再次调用call_once函数
		//但是此时g_flag已经被标记为“已调用”状态了,so此时另一个线程就不能再次调用此CreateInstance()函数了!
		//这样就deal 了单例设计模式中写多线程代码时所出现的问题!
		return m_instance;
	}
	void func() {
		static int nums = 0;
		nums++;
		cout << "test codes " <<nums<< endl;
	}

	class CGarHuishou {//类中嵌套另一个类,这个类专门用于析构单例类中的new的对象所占据的内存空间!
	public:
		~CGarHuishou() {
			if (MyCLS::m_instance) {//非空 再delete
				delete MyCLS::m_instance;
				MyCLS::m_instance = nullptr;
				cout << "~CGarHuishou()执行了!" << endl;
			}
		}
	};
};
//静态成员变量必须是类内声明,类外初始化
MyCLS* MyCLS::m_instance = nullptr;

//线程入口函数
void mythread() {
	cout << "我的线程开始执行了!" << endl;
	MyCLS* p_a = MyCLS::getInstance();//这里很可能就会出问题了!
	cout << "我的线程执行完毕了!" << endl;
	return;
}
int main(void) {
	//创建了2个线程,虽然这2个线程都分别走的是同一条通路,
	//但是,毕竟是2个线程,彼此互不相关
	//这时候问题就来了,当mytobj1这个线程1执行到MyCLS* p_a = MyCLS::getInstance();时
	//刚刚准备调用MyCLS类的getInstance函数中的m_instance = new MyCLS();这行代码时,还没运行呢
	//操作系统就切换到mytobj2这个线程2中去了,比如此时线程2刚好new了MyCLS对象后,操作系统又切换回
	//mytobj1这个线程1中去,那么此时线程1又会new一个MyCLS对象!那么此时就会有2个MyCLS对象的出现
	//这就出现问题了,因为单例类中只允许创建一个对象!
	thread mytobj1(mythread);
	thread mytobj2(mythread);
	mytobj1.join();
	mytobj2.join();
	return 0;
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-12-24 18:17:43  更:2021-12-24 18:19:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 13:23:32-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码