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的概念 临时对象构造时的抓捕 成员函数指针做线程函数 传递临时对象作为线程参数 创建的工作线程不止一个,线程根据编号来确定工作内容.每个线程都需 ...
随机推荐
- js & regex & var & highlight
js & regex & var & highlight let key = `ali`.toLocaleUpperCase(); let name = "阿里云计算 ...
- Vue & mobile UI components
Vue & mobile UI components https://github.com/vuejs/awesome-vue https://awesome-vue.js.org/ http ...
- DOMParser & SVG
DOMParser & SVG js parse html to dom https://developer.mozilla.org/zh-CN/docs/Web/API/DOMParser ...
- Flutter 使用Tabbar不要Title
原文 Demo 1 import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp ext ...
- Docker使用指南
上文简单介绍了docker,这边记录一下docker的使用. 一.Docker启停 1.启动docker systemctl start docker 2.关闭docker systemctl sto ...
- C++算法代码——奖学金
题目来自:http://218.5.5.242:9018/JudgeOnline/problem.php?id=1098 题目描述 某小学最近得到了一笔赞助,打算拿出其中一部分为学习成绩优秀的前5名学 ...
- 21_MySQL表外连接实战
-- 查询每名员工的编号.姓名.部门.月薪.工资等级.工龄.上司编号.上司姓名.上司部门? SELECT e.empno,#员工编号 e.ename,#员工姓名 e.deptno,#员工部门 e.sa ...
- restful风格的理解
简而言之,就是不同的命令响应不同的操作: 关注点在url中的不同参数,是因为不同的参数才使得不同的method去对应的不同的操作.
- void指针及指针的多次赋值的理解
1.void指针的类型转换 int A::functionCommamd(const DWORD _from,const DWORD _to,const DWORD Event_type,void * ...
- Tango with django 1.9 中文——3.Django基础
让我们开始运用Django.本章主要是给你一个关于创建新项目和新应用过程的概览.在本章的末尾,你将建立起一个简单的由Django驱动的网站. 3.1 配置测试 让我们测试以下你的Python和Djan ...