C++并发与多线程学习笔记--多线程数据共享问题
- 创建和等待多个线程
- 数据和共享问题分析
- 只读的数据
- 有读有写
- 其他案例
- 共享数据的保护案例代码
创建和等待多个线程
服务端后台开发就需要多个线程执行不同的任务。不同的线程执行不同任务,并返回执行结果。很多个线程都用同一个线程入口:
void myprint(int num)
{
cout << "线程开始执行了: " << num << endl;
cout << "My print id: "<<this_thread::get_id() << endl;
cout << "线程结束执行了" << endl;
return;
} int main()
{
vector<thread> workers;
//创建10个线程,线程的入口统一使用 void myprint(int num)
for (int i = 0; i < 10; i++) {
workers.push_back(thread(myprint, i)); //创建并开始执行线程
}
for (auto iter = workers.begin(); iter != workers.end(); ++iter) {
iter->join();
} cout << "Main Thread End!!!" << endl; return 0;
}
小结:
1)多个线程的执行顺序是乱的,跟操作系统内部的运行机制有关。
2)主线程等待所有子线程运行结束,最后主线程才结束。
3)用join写出来的程序才比较稳定,更容易写出稳定的程序。
4)专门用迭代器创建多个线程的写法,创建大量的线程进行管理,很方便。==>线程池的思路。
数据和共享问题分析
只读的数据
共享数据
vector<int> g_v = { 1,2,3 }; //共享数据
修改打印函数
void myprint(int num)
{ cout << "线程的ID为 " << this_thread::get_id() <<
"打印 g_v的值为" << g_v[0] << g_v[1] << g_v[2] << endl;
return;
}
虽然顺序不定,但是每次都成功读出了数组中的值。==>只读数据是安全稳定的,直接可以读。
有读有写
有读有些的线程,一旦代码写不好的时候,容易出问题,崩溃or报错。
两个线程往容器里面写,八个线程往容器里读,如果没有特别的处理,程序肯定崩溃。最简单的不崩溃处理,读的时候不能写,写的时候不能读。==>互斥锁的思路??
1)任务切换有各种诡异的事情发生,如程序崩溃。
std::mutex locker;
void myprint(int num)
{
locker.lock();
cout << "线程的ID为 " << this_thread::get_id() <<
"打印 g_v的值为" << g_v[0] << g_v[1] << g_v[2] << endl;
locker.unlock();
return;
}
其他案例
例1:假设定火车票,10个售票窗口。
1,2窗口同时卖票,如果座位已经有人订了,那么直接返回,告诉顾客已经有人坐了,否则订票。
1号窗口和2号窗口共享这些数据。
共享数据的保护案例代码
实际工作中的范例:网络游戏服务器开发,网络游戏服务器,这个服务器,最简单的有两个自己创建的线程:(实际中可以用线程池来做):
1) 用来收玩家的命令并把命令数据写到一个队列中,这个线程专门负责通过网络收数据。
2) 线程重队列中取出玩家发送的命令,解析,执行玩家的动作---抽卡!!!
3) 用数字表示玩家的动作
4) vector, list, list和vector的内部实现手段不一样,在底层虽然都是push_back(),但是在插入元素的时候,vector需要复制内存到新的空间,并且有[]操作符的重载,而list是数据结构中的双向链表,因此没有[],在频繁插入和删除的时候使用List,容器对于随机的插入和删除效率高。
使用List的时候
#include<list>
用成员函数作为线程函数的方法写线程。实际中,一般都把变量写在类中,符合面向对象的程序设计思想。
class ProcessRequest {
public:
//把命令加入到一个队列
void inMsgRecvQueue() {
for (int i = 0; i < 100000; ++i) {
cout << "插入一个元素" << endl;
m_msgRecvQueue.push_back(i); //假设这个队列表示玩家的命令 } //占用时间片
}
//把命令移出一个队列
void outMsgRecvQueue() { for (int i = 0; i < 100000; ++i) {
if (!m_msgRecvQueue.empty()) {
//消息不为空
int command = m_msgRecvQueue.front();
//尝试返回第一个元素,取出元素
m_msgRecvQueue.pop_front();
}
else
{
cout << "outMsgRecvQueue() 还执行,但是消息队列为空"<<i<< endl;
//消息队列为空
} } //占用时间片
cout << "end!!!!!" << endl;
} private:
std::list<int> m_msgRecvQueue; //容器,用于表示玩家的发送过来命令 };
主函数
ProcessRequest obj;
std::thread outWorker(&ProcessRequest::outMsgRecvQueue, &obj);
//第二参数是引用才是同一个对象,不能用detach(),否则不稳定
std::thread inWorker(&ProcessRequest::inMsgRecvQueue, &obj);
//两个线程创建完成之后,要保证对象是有意义的,用join
outWorker.join();
inWorker.join();
数据共享的理论,有读有写,不断地读和写,共享的消息队列,如果完全不控制,一定会出错。共享数据与锁,某个线程在操作的时候,其他线程需要等待。写的时候锁住,读的时候锁住。
1)互斥量。多线程一定会有互斥量,下回分解。。。
其他知识
vector和built-in数组类似,它拥有一段连续的内存空间,并且起始地址不变,因此它能非常好的支持随即存取,即[]操作符,但由于它的内存空间是连续的,所以在中间进行插入和删除会造成内存块的拷贝,另外,当该数组后的内存空间不够时,需要重新申请一块足够大的内存并进行内存的拷贝。这些都大大影响了vector的效率。
list就是数据结构中的双向链表(根据sgi stl源代码),因此它的内存空间可以是不连续的,通过指针来进行数据的访问,这个特点使得它的随即存取变的非常没有效率,因此它没有提供[]操作符的重载。但由于链表的特点,它可以以很好的效率支持任意地方的删除和插入。
deque是一个double-ended queue,它的具体实现不太清楚,但知道它具有以下两个特点:
它支持[]操作符,也就是支持随即存取,并且和vector的效率相差无几,它支持在两端的操作:push_back,push_front,pop_back,pop_front等,并且在两端操作上与list的效率也差不多。
因此在实际使用时,如何选择这三个容器中哪一个,应根据你的需要而定,一般应遵循下面
的原则:
1、如果你需要高效的随即存取,而不在乎插入和删除的效率,使用vector
2、如果你需要大量的插入和删除,而不关心随即存取,则应使用list
3、如果你需要随即存取,而且关心两端数据的插入和删除,则应使用deque。
参考文献
https://blog.csdn.net/bmjhappy/article/details/82228080
C++并发与多线程学习笔记--多线程数据共享问题的更多相关文章
- java进阶-多线程学习笔记
多线程学习笔记 1.什么是线程 操作系统中 打开一个程序就是一个进程 一个进程可以创建多个线程 现在系统中 系统调度的最小单元是线程 2.多线程有什么用? 发挥多核CPU的优势 如果使用多线程 将计算 ...
- 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁
什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...
- Java学习笔记-多线程-创建线程的方式
创建线程 创建线程的方式: 继承java.lang.Thread 实现java.lang.Runnable接口 所有的线程对象都是Thead及其子类的实例 每个线程完成一定的任务,其实就是一段顺序执行 ...
- java多线程学习笔记——详细
一.线程类 1.新建状态(New):新创建了一个线程对象. 2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中, ...
- JAVA多线程学习笔记(1)
JAVA多线程学习笔记(1) 由于笔者使用markdown格式书写,后续copy到blog可能存在格式不美观的问题,本文的.mk文件已经上传到个人的github,会进行同步更新.github传送门 一 ...
- 多线程学习笔记九之ThreadLocal
目录 多线程学习笔记九之ThreadLocal 简介 类结构 源码分析 ThreadLocalMap set(T value) get() remove() 为什么ThreadLocalMap的键是W ...
- Java多线程学习笔记(一)——多线程实现和安全问题
1. 线程.进程.多线程: 进程是正在执行的程序,线程是进程中的代码执行,多线程就是在一个进程中有多个线程同时执行不同的任务,就像QQ,既可以开视频,又可以同时打字聊天. 2.线程的特点: 1.运行任 ...
- C#多线程学习(一) 多线程的相关概念(转)
什么是进程?当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源.而一个进程又是由多个线程所组成的. 什么是线程?线程是程序中的一个执行流,每个线程都有自己的专有寄 ...
- C#多线程学习(一) 多线程的相关概念
什么是进程?当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源.而一个进程又是由多个线程所组成的. 什么是线程?线程是程序中的一个执行流,每个线程都有自己的专有寄 ...
随机推荐
- taro ENV & NODE_ENV & process.env
taro ENV & NODE_ENV & process.env https://github.com/NervJS/taro-ui/blob/dev/src/common/util ...
- redux & multi dispatch & async await
redux & multi dispatch & async await 同时发送多个 action, 怎么保证按序返回数据 dispatch multi actions http:/ ...
- vue的filter用法,检索内容
var app5 = new Vue({ el: '#app5', data: { shoppingList: [ "Milk", "Donuts", &quo ...
- ROS等下载时无法连接问题的解决方法
资料参考: https://blog.csdn.net/weixin_44692299/article/details/105869229
- window.onresize绑定事件以及解绑事件
问题描述 在Vue工程中,添加样式,部分需要做到自适应,需要添加resize事件,由于是单页面应用,如果组件初始化的时候绑定事件,在切换页面的时候不去注销事件,如果来回切换,会让resize事件执行多 ...
- 【Notes】现代图形学入门_02
跟着闫令琪老师的课程学习,总结自己学习到的知识点 课程网址GAMES101 B站课程地址GAMES101 课程资料百度网盘[提取码:0000] 光栅化 着色(Shading) 在图形学中,着色的定义可 ...
- Pycharm模块导入失败,带有红色波浪线。
在Pycharm中打开一个python开源工程,结果在导入库的部分一堆红色波浪线显示错误,并且按住Ctrl + 鼠标左击无法跳转到指定类或方法,如下图所示. 解决方法: (1)首先忽略掉这些报错,先运 ...
- 剑指 Offer 38. 字符串的排列 + 无重复元素的全排列
剑指 Offer 38. 字符串的排列 Offer_38 题目描述 解题思路 可以使用递归实现全排列,每次都确定一个数的位置,当所有位置的数都确定后即表示一个排列. 但是考虑到本题需要排除重复的排列, ...
- Mybatis系列全解(七):全息视角看Dao层两种实现方式之传统方式与代理方式
封面:洛小汐 作者:潘潘 一直以来 他们都说为了生活 便追求所谓成功 顶级薪水.名牌包包 还有学区房 · 不过 总有人丢了生活 仍一无所获 · 我比较随遇而安 有些事懒得明白 平日里问心无愧 感兴趣的 ...
- ant-design-vue中table自定义列
1. 使用背景 在项目中使用ant-vue的a-table控件过程中,需要显示序号列或者在列中显示图片,超链,按钮等UI信息.经过查询文档customCell和customRender可以实现以上需求 ...