【lesson55】线程同步

线程同步

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步

例一我们之前的抢票代码,一个线程把票全抢完了,它错了吗?没错!但是不合理。

例二:我们要去华为店里买华为Mate40,
第一天去华为专卖店,我们问店员有没有货,店员说没有,我们立马就转身离开。
第二天再去我们问店员有没有货,店员说没有,我们再次转身离开。
就这样连续去30天左右,店里终于有货了,我们买了就走。
上面这样有错吗?没错,但是不合理。

1.例子一频繁的申请到资源
2.例子二太过于浪费我自己和对方的资源(时间)
例子一和二都没有错但是不合理。

例子一造成别人的饥饿问题。

而线程同步主要就是为了解决,访问临界资源合理性的问题。
故事
学校有一间自习室,每天早上8:00~晚上21:00开放,但是这间自习室只允许一个人进入,墙上有一把钥匙,钥匙需要大家抢夺,谁抢到钥匙谁就拥有自习室。

小明某一天早早的来到了自习室,拿了墙上的钥匙,就打开门,然后关上门把门反锁,再把钥匙放在兜里。
自习了几个小时小明准备去趟厕所,打开门发现人有点多,就把门反锁了然后把钥匙一起带走,上完厕所就再回来,自习室就还是属于自己的。
晚上要吃晚饭了,小明准备回去,一打开门发现门口人乌泱泱的,小明立马反悔,觉得还是再学一会吧。
又学了一会小明觉得该回去了,再次打开门,然后把钥匙挂墙上,可是刚挂上去小明又后悔了,而又因为小明里钥匙最近所以他又抢到了钥匙,然后回去再学了一会。

听完上面故事大家觉得合理吗?不合理,小明凭什么一直能抢到钥匙。
有人举报了小明,所以自习室管理员引入规则:
1.自习室外面的人必须排好队,不能乌泱泱的一群人挤在一起
2.人从自习室出来以后不能立马再次申请锁,得必须排到队伍的末尾。

按照一定顺序,进行临界资源的访问我们称之为线程同步。

如何保证顺序性?
条件变量
我们之前申请临界资源前---->都需要做临界资源是否存在的检测---->而检测的本质:也是访问临界资源!---->所以我们之前临界资源的检测也在加锁和解锁之间---->常规方式要检测条件就绪,注定了我们必须频繁申请和释放锁---->有没有办法让我们的线程检测到资源不就绪的时候。
1.不要让线程在频繁的自己检测了,我们等待资源就绪
2.当条件就绪的时候,通知对应的线程,让他来进行资源申请和访问!

之前买华为手机的例子,当店里没有货的时候,我们留下店员的联系方式或者留下自己的联系方式,让店员有货了在通知自己过去买手机。

条件变量:
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

没加条件变量前的代码:

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>#define TNUM 4typedef void (*func)(std::string name);struct ThreadData
{
public:ThreadData(std::string name, func func): _name(name),_func(func){}public:std::string _name;func _func;
};void fun1(std::string name)
{while (true){std::cout << name << " running ------- a" << std::endl;sleep(1);}
}void fun2(std::string name)
{while (true){std::cout << name << " running ------- b" << std::endl;sleep(1);}
}void fun3(std::string name)
{while (true){std::cout << name << " running ------- c" << std::endl;sleep(1);}
}void fun4(std::string name)
{while (true){std::cout << name << " running ------- d" << std::endl;sleep(1);}
}void *Entry(void *args)
{ThreadData *td = (ThreadData *)args;td->_func(td->_name);delete td;
}int main()
{pthread_t tid[TNUM];func func[TNUM] = {fun1, fun2, fun3, fun4};for (int i = 0; i < TNUM; i++){std::string name = "Thread";name += std::to_string(i + 1);ThreadData *td = new ThreadData(name, func[i]);pthread_create(tid + i, nullptr, Entry, (void *)td);}for (int i = 0; i < TNUM; i++){pthread_join(tid[i], nullptr);}return 0;
}

运行结果:
在这里插入图片描述
我们看到线程是随机运行的,谁都有可能被CPU调度,那么我们如何让他顺序运行呢?
添加条件变量:
代码:

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>#define TNUM 4typedef void (*func)(std::string name,pthread_mutex_t *mtx, pthread_cond_t *cond);struct ThreadData
{
public:ThreadData(std::string name, func func, pthread_mutex_t *mtx, pthread_cond_t *cond): _name(name),_func(func),_mtx(mtx),_cond(cond){}public:std::string _name;func _func;pthread_mutex_t *_mtx;pthread_cond_t *_cond;
};void fun1(std::string name,pthread_mutex_t *mtx, pthread_cond_t *cond)
{while (true){pthread_cond_wait(cond,mtx);std::cout << name << " running ------- a" << std::endl;//sleep(1);}
}void fun2(std::string name,pthread_mutex_t *mtx, pthread_cond_t *cond)
{while (true){pthread_cond_wait(cond,mtx);std::cout << name << " running ------- b" << std::endl;//sleep(1);}
}void fun3(std::string name,pthread_mutex_t *mtx, pthread_cond_t *cond)
{while (true){pthread_cond_wait(cond,mtx);std::cout << name << " running ------- c" << std::endl;//sleep(1);}
}void fun4(std::string name,pthread_mutex_t *mtx, pthread_cond_t *cond)
{while (true){pthread_cond_wait(cond,mtx);std::cout << name << " running ------- d" << std::endl;//sleep(1);}
}void *Entry(void *args)
{ThreadData *td = (ThreadData *)args;td->_func(td->_name,td->_mtx,td->_cond);delete td;return nullptr;
}int main()
{pthread_mutex_t mtx;pthread_cond_t cond;pthread_mutex_init(&mtx, nullptr);pthread_cond_init(&cond, nullptr);pthread_t tid[TNUM];func func[TNUM] = {fun1, fun2, fun3, fun4};for (int i = 0; i < TNUM; i++){std::string name = "Thread";name += std::to_string(i + 1);ThreadData *td = new ThreadData(name, func[i], &mtx, &cond);pthread_create(tid + i, nullptr, Entry, (void *)td);}while(true){std::cout << "Wake up thread run code ......." << std::endl;pthread_cond_signal(&cond);sleep(1);}for (int i = 0; i < TNUM; i++){pthread_join(tid[i], nullptr);}pthread_mutex_destroy(&mtx);pthread_cond_destroy(&cond);return 0;
}

运行结果:
在这里插入图片描述
我们看到线程变的井然有序,一个一个的执行。
在这里插入图片描述
pthread_cond_signal是一个一个的唤醒线程,我们还可以用pthread_cond_broadcast一批一批的唤醒。
修改代码片段
在这里插入图片描述
运行代码:
在这里插入图片描述
如果我们想让线程运行10次就退出,那么代码该如何更改呢?
我们在全局定义一个bool类型的quit。然后每个线程用quit判断退出条件即可。
代码:

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>#define TNUM 4bool quit = false;typedef void (*func)(std::string name,pthread_mutex_t *mtx, pthread_cond_t *cond);struct ThreadData
{
public:ThreadData(std::string name, func func, pthread_mutex_t *mtx, pthread_cond_t *cond): _name(name),_func(func),_mtx(mtx),_cond(cond){}public:std::string _name;func _func;pthread_mutex_t *_mtx;pthread_cond_t *_cond;
};void fun1(std::string name,pthread_mutex_t *mtx, pthread_cond_t *cond)
{while (!quit){pthread_cond_wait(cond,mtx);std::cout << name << " running ------- a" << std::endl;//sleep(1);}
}void fun2(std::string name,pthread_mutex_t *mtx, pthread_cond_t *cond)
{while (!quit){pthread_cond_wait(cond,mtx);std::cout << name << " running ------- b" << std::endl;//sleep(1);}
}void fun3(std::string name,pthread_mutex_t *mtx, pthread_cond_t *cond)
{while (!quit){pthread_cond_wait(cond,mtx);std::cout << name << " running ------- c" << std::endl;//sleep(1);}
}void fun4(std::string name,pthread_mutex_t *mtx, pthread_cond_t *cond)
{while (!quit){pthread_cond_wait(cond,mtx);std::cout << name << " running ------- d" << std::endl;//sleep(1);}
}void *Entry(void *args)
{ThreadData *td = (ThreadData *)args;td->_func(td->_name,td->_mtx,td->_cond);delete td;return nullptr;
}int main()
{pthread_mutex_t mtx;pthread_cond_t cond;pthread_mutex_init(&mtx, nullptr);pthread_cond_init(&cond, nullptr);pthread_t tid[TNUM];func func[TNUM] = {fun1, fun2, fun3, fun4};for (int i = 0; i < TNUM; i++){std::string name = "Thread";name += std::to_string(i + 1);ThreadData *td = new ThreadData(name, func[i], &mtx, &cond);pthread_create(tid + i, nullptr, Entry, (void *)td);}int cnt = 10;while(cnt){std::cout << std::endl;std::cout << "Wake up thread run code ......." <<  cnt-- << std::endl;//pthread_cond_signal(&cond);pthread_cond_broadcast(&cond);sleep(1);}quit = true;for (int i = 0; i < TNUM; i++){pthread_join(tid[i], nullptr);}pthread_mutex_destroy(&mtx);pthread_cond_destroy(&cond);return 0;
}

运行结果:
在这里插入图片描述
我们看到这最后卡住了是为什么呢?因为我们虽然是quit但是,所有线程还在条件变量上等待呢,而条件变量是在加锁和解锁之间的,我们虽然退出了但是锁并没有处理好。
所以我们在每个线程都要加上加锁解锁的逻辑。
代码:

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>#define TNUM 4bool quit = false;typedef void (*func)(std::string name,pthread_mutex_t *mtx, pthread_cond_t *cond);struct ThreadData
{
public:ThreadData(std::string name, func func, pthread_mutex_t *mtx, pthread_cond_t *cond): _name(name),_func(func),_mtx(mtx),_cond(cond){}public:std::string _name;func _func;pthread_mutex_t *_mtx;pthread_cond_t *_cond;
};void fun1(std::string name,pthread_mutex_t *mtx, pthread_cond_t *cond)
{while (!quit){pthread_mutex_lock(mtx);pthread_cond_wait(cond,mtx);std::cout << name << " running ------- a" << std::endl;pthread_mutex_unlock(mtx);//sleep(1);}
}void fun2(std::string name,pthread_mutex_t *mtx, pthread_cond_t *cond)
{while (!quit){pthread_mutex_lock(mtx);pthread_cond_wait(cond,mtx);std::cout << name << " running ------- b" << std::endl;pthread_mutex_unlock(mtx);//sleep(1);}
}void fun3(std::string name,pthread_mutex_t *mtx, pthread_cond_t *cond)
{while (!quit){pthread_mutex_lock(mtx);pthread_cond_wait(cond,mtx);std::cout << name << " running ------- c" << std::endl;pthread_mutex_unlock(mtx);//sleep(1);}
}void fun4(std::string name,pthread_mutex_t *mtx, pthread_cond_t *cond)
{while (!quit){pthread_mutex_lock(mtx);pthread_cond_wait(cond,mtx);std::cout << name << " running ------- d" << std::endl;pthread_mutex_unlock(mtx);//sleep(1);}
}void *Entry(void *args)
{ThreadData *td = (ThreadData *)args;td->_func(td->_name,td->_mtx,td->_cond);delete td;return nullptr;
}int main()
{pthread_mutex_t mtx;pthread_cond_t cond;pthread_mutex_init(&mtx, nullptr);pthread_cond_init(&cond, nullptr);pthread_t tid[TNUM];func func[TNUM] = {fun1, fun2, fun3, fun4};for (int i = 0; i < TNUM; i++){std::string name = "Thread";name += std::to_string(i + 1);ThreadData *td = new ThreadData(name, func[i], &mtx, &cond);pthread_create(tid + i, nullptr, Entry, (void *)td);}int cnt = 10;while(cnt){std::cout << std::endl;std::cout << "Wake up thread run code ......." <<  cnt-- << std::endl;//pthread_cond_signal(&cond);pthread_cond_broadcast(&cond);sleep(1);}quit = true;for (int i = 0; i < TNUM; i++){pthread_join(tid[i], nullptr);}pthread_mutex_destroy(&mtx);pthread_cond_destroy(&cond);return 0;
}

运行结果:
在这里插入图片描述
我们看到这最后卡住了是为什么呢?因为我们虽然是quit但是,所有线程还在条件变量上等待呢,所以我们需要在退出后再进行一次pthread_cond_broadcast();
在这里插入图片描述
运行结果:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/684489.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【springboot+vue项目(十六)】基于Oauth2的SSO单点登录(三)SpringSecurity+Jwt 整合Aouth2(客户端部分)

要将Spring Security与基于OAuth 2.0的第三方认证系统进行整合&#xff0c;你需要执行以下步骤&#xff1a; 配置Spring Security以使用OAuth 2.0&#xff1a; 添加依赖项&#xff1a;在项目的构建文件&#xff08;如Maven的pom.xml&#xff09;中添加适当的依赖项&#xff0c;…

top100-回溯算法专题

回溯算法和深度优先遍历 回溯法采用试错的思想&#xff0c;它尝试分布的去解决一个问题。在分布解决问题的过程中&#xff0c;当它通过尝试发现现有的分布答案不能得到有效的正确的解答的时候&#xff0c;它将取消上一步甚至上级不的计算&#xff0c;再通过其他的可能的分布解答…

Duilib 的WinMain函数学习

之前跑了一个基本例子;接下来准备再做一些; 看着它的WinMain函数里面,有几句不知道需不需要; 它是这样的;从别的示例里面来的; int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int nCmdShow) {CPaintManagerUI::SetIn…

Qt Creator 继承分文件编写代码流程实现简单案列

Qt Creator 继承分文件流程实现简单案列 打开Qt Creator&#xff0c;新建c项目 添加类 完成之后&#xff0c;会自动生成.h和.cpp文件 一、animal.h文件 主要用来写类&#xff0c;包括成员变量和函数 #ifndef ANIMAL_H #define ANIMAL_H #include <iostream> #inclu…

高效货运 - 华为OD统一考试(C卷)

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 200分 题解&#xff1a; Java / Python / C 题目描述 老李是货运公司承运人&#xff0c;老李的货车额定载货重量为wt&#xff1b;现有两种货物&#xff0c;货物A单件重量为wa&#xff0c;单件运费利润为pa&#xff0c…

【matalab】基于Octave的信号处理与滤波分析案例

一、基于Octave的信号处理与滤波分析案例 GNU Octave是一款开源软件&#xff0c;类似于MATLAB&#xff0c;广泛用于数值计算和信号处理。 一个简单的信号处理与滤波分析案例&#xff0c;说明如何在Octave中生成一个有噪声的信号&#xff0c;并设计一个滤波器来去除噪声。 首…

识别盐构造在预先确定造山带动力学和几何形态方面的重要性和控制作用

近几十年来&#xff0c;理解盐岩的变形已成为许多含盐褶皱冲断带中日益受到关注的研究课题。越来越多的研究指出&#xff0c;继承性正断层及与之相连的盐构造在预先确定造山带动力学和几何形态方面的重要性和控制作用&#xff08;例如&#xff0c;在北石灰岩阿尔卑斯地区有Gran…

从数字孪生到智慧城市:科技引领下的城市未来展望

一、引言 随着科技的飞速发展&#xff0c;数字孪生和智慧城市已成为当今世界城市发展的重要趋势。数字孪生通过建立物理世界的数字模型&#xff0c;为城市管理和规划提供了前所未有的可能性&#xff1b;而智慧城市则借助先进的信息通信技术&#xff0c;使城市运行更加高效、便…

关于 TI Bq40Z551 Cell Swelling Protection的理解

“Cell Swelling Protection”&#xff08;电池膨胀保护&#xff0c;俗称鼓包&#xff09;是指一种保护措施&#xff0c;用于防止充电时电池发生过度膨胀的情况。 当充电电池过度膨胀时&#xff0c;可能会对设备的性能和安全造成威胁&#xff0c;包括电池的寿命缩短、电池损坏…

python调用智谱ai 大模型的完整步骤 (国内的 AI 大模型 对话)

要使用Python调用智谱AI的API进行异步调用&#xff0c;您需要遵循以下步骤&#xff1a; 1. **获取API密钥**&#xff1a; - 您需要从智谱AI平台获取一个API密钥&#xff08;API Key&#xff09;&#xff0c;这个密钥将用于所有API请求的身份验证。 2. **安装…

Springmvc 的参数绑定之list集合

标签中name属性的值就是pojo类的属性名 参数绑定4 list [对象] <form action"teaupd.do" method"post"> <c:forEach items"${list}" var"tea" varStatus "status"> 教师编号&#xff1a;<input…

C语言学习day14:跳转语句

今天学习的跳转语句主要是三种&#xff1a; break continue goto 上一篇文章已经说过了break和continue break&#xff1a;结束这个循环 continue&#xff1a;结束当前的循环迭代&#xff0c;进行下一次的迭代 看看二者代码的区别 代码&#xff08;break&#xff09;&am…

嵌入式I2C 信号线为何加上拉电阻(图文并茂)

IIC 是一个两线串行通信总线&#xff0c;包含一个 SCL 信号和 SDA 信号&#xff0c;SCL 是时钟信号&#xff0c;从主设备发出&#xff0c;SDA 是数据信号&#xff0c;是一个双向的&#xff0c;设备发送数据和接收数据都是通过 SDA 信号。 在设计 IIC 信号电路的时候我们会在 SC…

相机图像质量研究(19)常见问题总结:CMOS期间对成像的影响--Sensor Noise

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

C# VS2022+WinForm+Oracle19.3+Excel,根据数据库表定义书生成SQL

目标&#xff1a; 用Excel写数据库的表的定义书&#xff0c;用该工具生成SQL&#xff0c;在客户端执行&#xff0c;把表结构导入数据库&#xff0c;生成真正的表 Github代码下载 目录 0.完成下面开发环境的准备1 操作系统Win11 专业版 21H22 oracle 19.33 Visual Studio Commun…

22.常用的DOM操作

1&#xff09;DOM 节点的获取 DOM 节点的获取的API及使用&#xff1a; getElementById // 按照 id 查询 getElementsByTagName // 按照标签名查询 getElementsByClassName // 按照类名查询 querySelectorAll // 按照 css 选择器查询// 按照 id 查询 var imooc document.getE…

C++,stl,常用遍历查找算法

目录​​​​​​​ 1.常用遍历算法 for_each transform 2.常用查找算法 find find_if adjacent_find binary_search count count_if 1.常用遍历算法 for_each #include<bits/stdc.h> using namespace std;void print(int v) {cout << v << ; }…

AVL_Cruise后处理(中文

下载 https://download.csdn.net/download/jintaihu/68332464

Linux--编译器-gcc/g++使用

目录 前言 1.看一段样例 2.程序的翻译过程 1.第一个阶段&#xff1a;预处理 2.第二个阶段&#xff1a;编译 3.第三个阶段&#xff1a;汇编 4.第四个阶段&#xff1a;链接 3.程序的编译为什么是这个样子&#xff1f; 4. 关于编译器 5.链接&#xff08;动静态链接&#x…

Three.js学习7:dat.GUI 参数控制

-----------------------------华丽的分割线--------------------- 相关代码均已上传到 gitee 中&#xff1a;myThree: 学习 Three.js &#xff0c;努力加油~&#xff01; Gitee 静态演示地址&#xff1a;Three JS 演示页面 -----------------------------华丽的分割线------…