C++并发与多线程学习笔记--单例设计模式、共享数据分析
- 设计模式
- 共享数据分析
- call_once
设计模式
开发程序中的一些特殊写法,这些写法和常规写法不一样,但是程序灵活,维护起来方便,别人接管起来,阅读代码的时候都会很痛苦。用设计模式理念写出来的代码很晦涩,国内的05~10年的时候有一本“Head First”,写程序的时候谈到设计模式。
项目开发经验+模块开发经验=设计模式
先有开发需求,然后把一个大的工程拆分很很多小的模块,然后演变出设计模式。当设计模式传到国内来的时候,很多程序员写代码把设计模式往代码中套,使得一个很小的程序的变得复杂,而且源码晦涩,本来设计模式是为了把大的东西拆成小的东西,编程的时候,各个模块方便管理。而不是生搬硬套,写程序的时候要“活学活用”。
单例设计模式,使用的频率比较高:在整个项目中,有某个或者某些特殊的类,属于该类的对象,我只能创建一个,多了创建不了。单例是写法比较特殊的类,整个项目中只能有一个,用于配置文件之类的操作。
单例类;
class MyCAS //这是一个单例类
{
private:
MyCAS(){} //构造函数私有化了
//不能创建对象
private:
static MyCAS *m_instance; //静态成员变量 public:
static MyCAS *GetInstance()
{
if(m_instance ==nullptr)
{
m_instance = new MyCAS();
}
return m_instance;
}
};
在main函数之外,类静态变量初始化
MyCAS *MyCAS::m_instance = nullptr; //静态变量的初始化
在main函数内
MyCAS *p_a = MyCAS::GetInstance(); //创建一个对象,返回该类对象的指针
如果再写 MyCAS *p_b = MyCAS::GetInstance(),此时仍然指向同一个实例,返回唯一的对象指针。
析构函数写法,此时还有析构代码要写上,在原来的类中定一个新类,类中套类,用来释放对象。
public:
static MyCAS *GetInstance()
{
if(m_instance ==nullptr)
{
m_instance = new MyCAS();
static GarRelease cl; //程序退出的时候析构这个类,自动释放资源
}
return m_instance;
} class GarRelease
{
~GarRelease()
{
if(MyCAS::m_instance)
{
delete MyCAS::m_instance;
MyCAS::m_instance = nullptr;
}
}
};
小结:因为把构造函数私有化了,不能通过对象直接生成对象,只能通过一个static来生成,并且构造指针指向同一个对象,整个类的写法就是一种设计模式。
共享数据分析
单例模式会面临一个问题,GetInstance被多个线程使用,如果一个数据是只读的,多个线程之间不需要互斥,但是有一个问题,单例对象创建的时候,在创建之前把对象初始化,载入数据,后续只读。
实际中可能面临的问题:需要我们在自己创建的线程中创建单例类,这种线程不止一个。
可能需要面临GetInstance()需要互斥的情况:
如果两个线程用同一个入口函数:
void myThread()
{
MyCAS *p_a = MyCAS::GetInstance();
}
会出现问题:两个线程是同一个入口函数,但是这个两个线程,不管用的是哪个入口函数,两个线程意味着两个流程(通路),同时开始执行这个函数,此时需要一个互斥量防止多个线程同时调用GetInstance()函数
std::unique_lock<std::mutex> mymutex(resource_mutex); //自动加锁,出了作用域之外自动解锁
程序写完,还是会被怼,程序写完里面有很多地方都要调用GetInstane(),拿到对象指针,如果调用程序很频繁使用,效率非常低。在最外面一层包裹一个判断条件,如果:
a)if(m_instance!=nullptr) 条件成立,则肯定表示m_instance 已经被new过了;
b)if(m_instance ==nullptr) 条件成立,不代表m_instance 一定没被new过;
c)双重锁定;
static MyCAS *GetInstance()
{
if(m_instance ==nullptr)
{
std::unique_lock<std::mutex> mymutex(source_mutex)
if(m_instance ==nullptr)
{
m_instance = new MyCAS();
static GarRelease cl;
}
}
return m_instance;
}
call_once
std::call_once()是一个函数模板,这个也是C++11引入的函数,其中第二个参数,就是一个函数名,第二个参数就是一个函数名a(),call_once()能够保证函数a只被调用一次,比如说有两个线程,都调用了函数a,正常情况下被调用了两次,如果用call_once,就能保证函数只被调用了一次,如果把核心的共享数据代码,(new 一个对象)。
单例对象在多线程的情况下,初始化需要mutex,call_once具备互斥量这种能力,而且效率上比互斥量消耗的资源更少。call_once需要与一个标记结合使用,std::once_flag,其实这是一个结构,就可以看成一个标记。通过这个标记来决定对应的函数是否执行,调用call_once成功后,call_once就把这个标记设置为已调用的状态,后续再次调用call_once(),只要标记被设置为“已调用”状态,那么对应的函数a就不会在被执行了。
std::once_flag g_flag; //这是个系统定义的标记
完整的写法
class MyCAS
{
static void CreateInstance(); //只被调用一次的函数
{
m_instance = new MyCAS();
static GarRelease cl;
}
static MyCAS *GetInstance()
{ std::call_once(g_flag, CreateInstance);
return m_instance;
} }
假设两个线程都同时开始执行GetInstance(),同时执行到std::call_once(),call_once就好像一个锁,其中一个线程调用,另一个线程就要等当前这个线程执行完毕,才会去决定是否调用CreateInstance(),此时的call_once的标记已经被改变。
参考文献
C++并发与多线程学习笔记--单例设计模式、共享数据分析的更多相关文章
- 七、单例设计模式共享数据分析、解决、call_once
一.设计模式大概谈 代码的一些写法,与常规的写法不太一样,程序灵活,维护起来很方便,但是别人接管.阅读代码很痛苦. 用设计模式理念写出来的代码很晦涩.<< head first>&g ...
- Java学习笔记——单例设计模式Singleton
单例设计模式:singleton 解决的问题: 确保程序在运行过程中,某个类的实例instance只有一份. 特点: 1 构造函数私有化 2 自己内部声明自己 3 提供一个public方法,负责实例化 ...
- java基础学习之单例设计模式学习
最近狂补java基础的我重新学习了下单例,下面直接贴出代码,以作备忘 package com.darling.single; /** * 单例模式 * 单例即在内存中只存在该类的一个实例,要想实现这个 ...
- C++并发与多线程学习笔记--基本概念和实现
基本概念 并发 可执行程序.进程.线程 学习心得 并发的实现方法 多进程并发 多线程并发 总结 C++标准库 基本概念 (并发.进程.线程)区分C++初级编程和中高级编程 并发 两个或者更多的任务同时 ...
- C++并发与多线程学习笔记--future成员函数、shared_future、atomic
std::future的其他成员函数 std::shared_future 原子操作.概念.基本用法 多线程主要是为了执行某个函数,本文的函数的例子,采用如下写法 int mythread() { c ...
- C++并发与多线程学习笔记--互斥量、用法、死锁概念
互斥量(mutex)的基本概念 互斥量的用法 lock(), unlock() std::lock_guard类模板 死锁 死锁演示 死锁的一般解决方案 std::lock()函数模板 std::lo ...
- C++并发与多线程学习笔记--多线程数据共享问题
创建和等待多个线程 数据和共享问题分析 只读的数据 有读有写 其他案例 共享数据的保护案例代码 创建和等待多个线程 服务端后台开发就需要多个线程执行不同的任务.不同的线程执行不同任务,并返回执行结果. ...
- C++并发与多线程学习笔记--atomic
std::atomic std::async std::atomic 一般atomic原子操作,针对++,--,+=,^=是支持的,其他结果可能不支持. 注意 std::atomic<int&g ...
- C++并发与多线程学习笔记--参数传递详解
传递临时对象 陷阱 总结 临时对象作为线程参数 线程id的概念 临时对象构造时的抓捕 成员函数指针做线程函数 传递临时对象作为线程参数 创建的工作线程不止一个,线程根据编号来确定工作内容.每个线程都需 ...
随机推荐
- queueMicrotask & EventLoop & macrotask & microtask
queueMicrotask https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/queueMicro ...
- alipay 小程序开发教程
alipay 小程序开发教程 https://opendocs.alipay.com/mini/00ccmd 或访问开放平台:https://opendocs.alipay.com/mini/00cc ...
- how to get selected option text in javascript
how to get selected option text in javascript refs https://developer.mozilla.org/en-US/docs/Web/API/ ...
- js in depth: Object & Function & prototype & __proto__ & constructor & classes inherit
js in depth: Object & Function & prototype & proto & constructor & classes inher ...
- Unity 定点投射固定高度抛物线
假设同一平面中有AB两点,A点向B点水平射击,很容易想象子弹会沿由A指向B的向量方向前进,经过时间t后到达B点,若此时A点不再水平射击,改为以抛物线的方式向B点投射,同样需要在时间t后击中B点,那么如 ...
- JavaScript高级:JavaScript面向对象,JavaScript内置对象,JavaScript BOM,JavaScript封装
知识点梳理 课堂讲义 1.JavaScript面向对象 1.1.面向对象介绍 在 Java 中我们学习过面向对象,核心思想是万物皆对象. 在 JavaScript 中同样也有面向对象.思想类似. 1. ...
- io流+网络+线程池 实现简单的多客户端与服务器端通信
1 import java.io.IOException; 2 import java.io.InputStream; 3 import java.io.OutputStream; 4 import ...
- python进阶(3)序列化与反序列化
序列化与反序列化 按照某种规则,把内存中的数据保存到文件中,文件是一个字节序列,所以必须要把内存数据转换成为字节序列,输出到文件,这就是序列化:反之,从文件的字节恢复到内存,就是反序列化: pytho ...
- Lambad表达式--Java8新特性
1.概述 Lambda是一个匿名函数,是java8的一个新特性.可以对接口进行非常简洁的实现.但它要求接口中只能有一个抽象方法,原因是lambda只能实现一个方法.另外,需要在接口上添加注解@Func ...
- java与freemarker遍历map
一.java遍历MAP /** * 1.把key放到一个集合里,遍历key值同时根据key得到值 (推荐) */ Set set =map.keySet(); Iterator it=set.iter ...