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#多线程学习(一) 多线程的相关概念
什么是进程?当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源.而一个进程又是由多个线程所组成的. 什么是线程?线程是程序中的一个执行流,每个线程都有自己的专有寄 ...
随机推荐
- Java REPL & JShell
Java REPL & JShell Java 11 JShell Java Shell https://www.infoq.com/articles/jshell-java-repl/ Th ...
- c++ 获取兄弟窗口
// 返回给定窗口上方窗口的句柄. HWND prevSibling = GetWindow((HWND)0x1011C, GW_HWNDPREV); printf("%x\n", ...
- 07.k近邻算法kNN
1.将数据分为测试数据和预测数据 2.数据分为data和target,data是矩阵,target是向量 3.将每条data(向量)绘制在坐标系中,就得到了一系列的点 4.根据每条data的targe ...
- IdentityServer4之持久化很顺手的事
前言 原计划打算在春节期间多分享几篇技术文章的,但到最后一篇也没出,偷懒了吗?算是吧,过程是这样的:每次拿出电脑,在孩姥姥家的院子总有阳光沐浴,看不清屏幕,回屋又有点冷(在强行找理由),于是又带着娃遛 ...
- css3自动换行排列
如果一行放不下就会自动换行 display: flex; flex-wrap: wrap; 示例 : html <div class="container"> < ...
- uni-app创建项目
下载 HBuilderX 下载地址(https://www.dcloud.io/hbuilderx.html) HBuilderX是通用的前端开发工具,但为uni-app做了特别强化. 创建uni ...
- 剑指 Offer 57. 和为s的两个数字 + 二分法 + 双指针
剑指 Offer 57. 和为s的两个数字 Offer_57 题目详情 使用二分法 package com.walegarrett.offer; /** * @Author WaleGarrett * ...
- javascript处理HTML的Encode(转码)和解码(Decode)
HTML的Encode(转码)和解码(Decode)在平时的开发中也是经常要处理的,在这里总结了使用javascript处理HTML的Encode(转码)和解码(Decode)的常用方式 一.用浏览器 ...
- 学习版pytest内核测试平台开发万字长文入门篇
前言 2021年,测试平台如雨后春笋般冒了出来,我就是其中一员,写了一款pytest内核测试平台,在公司落地.分享出来后,有同学觉得挺不错,希望能开源,本着"公司代码不要传到网上去,以免引起 ...
- Spring AOP的源码流程
一.AOP完成日志输出 1,导入AOP模块 <dependency> <groupId>org.springframework</groupId> <arti ...