当前位置: 首页 > news >正文

Linux线程:编织并发的梦幻世界

目录

🚩引言

🚩听故事,引概念

🚩生产者消费者模型

 🚀再次理解生产消费模型

 🚀挖掘特点

 🚩条件变量

🚀条件变量常用接口

 🚀条件变量的原理


🚩引言

上一篇博客,我们集中讨论了Linux线程互斥相关的概念,但随着互斥锁的使用,也容易产生线程饥饿问题,所以我们有必要学习一下Linux线程同步相关的概念。接下来我们开始学习。

🚩听故事,引概念

假设在学校有一个VIP学霸自习室,这个自习室非常的豪华,但是只有一张桌子,一次只能允许一个人去上自习,这个自习室的钥匙就在门口放着,谁离钥匙近,谁就获得钥匙,就可以在利用这个自习室自习。有一天,同学张三为了在自习室中学习,享受那种超棒的学习环境,他早上5点就爬了起来。所以,等到他来到自习室门口时,没有人。他非常高兴的取得了钥匙,开开门,然后把门一反锁,开始自习。一转眼上午了,张三感觉肚子饿了,但他不想放弃他早起得到的机会,于是,他就把门锁上了,然后随身带着钥匙,门口的同学看着他的行为都非常的愤怒,但是又无奈,谁让人家来的早呢!不一会儿,张三吃完饭回来了,开开门,继续上自习。这家伙有闷头学了3个小时,突然肚子痛,要上厕所,他又带着钥匙上厕所。上完之后继续学习。可能学的时间太长了,不想学了,有好几次都开门准备放回钥匙,但每次都想到:下次不知道什么时候才能再次在这里上自习了。他放回钥匙之际,此时,他是离要是最近的人,对钥匙的竞争能力最强,所以他又获得了钥匙即自习室的使用权。张三的行为错了吗?他没错,但是不合理

门外的同学看见他这么干都非常生气,决定把这个情况反映给值班的老师,老师认为这样就是不太好,决定结束自习的同学要想再次获得自习室的使用权,必须要排队。

张三照常把钥匙放了回去,正当他想伸手再次取得钥匙时,旁边的老师说:"去排队去"。张三无奈的去排队去了。心想:“卧槽,失算了,之前不是这样的规则呀”。


听完这个故事,有些问题我们来思考这样几个问题:

  1. 按照之前的规则,张三同学反复申请这间自习室的使用权,是建立这间自习室的初衷吗?不是。那对其他同学公平吗?不公平,有的同学可能在门口等了一天,都没有使用到这间自习室。
  2. 这间自习室的钥匙就像是一把互斥锁,自习室就像是临界资源,申请这间自习室的使用权的同学就像是不同的执行流。所以,也会发生有的执行流因为竞争能力太强,就像这个同学张三,总是可以获得这把锁,然后进行访问。但是这样就会造成其他的执行流因为竞争不到对应的锁,而处于线程饥饿状态。这种状态是不利于充分利用资源的。在我们之前的抢票的代码中,我们也发现总是那一个执行流在抢票,所以这个问题要解决。而解决方案就是Linux线程同步就是让执行流按照一定的顺序(不一定是绝对的顺序)来获得访问相关临界资源的权利。

🚩生产者消费者模型

 在现实生活中,我们学生就是典型的消费者,而生产者就是工厂。

假设,有一天我想吃火腿肠了,我就跑到一个加工火腿肠的工厂,对那里的工作人员说:“给我加工火腿肠,多少钱,我给你”。工作人员听了我说的话,一定以为我是个傻子。这是因为制作火腿肠需要机器,而打开机器需要成本的,一根火腿肠指定是远远不够的,今天我来了,买了一根火腿肠,给我现场加工了,明天别人来了,又只要一根。我相信这个工厂早晚得倒闭。

所以,在生产者和消费者之间,一定还存在超市这样一个交易场所。超市的作用是集中需求,分发产品,是对生产者生产的商品的临时保存。超市存在的另一个原因是:工厂一般远离消费者,超市的存在可以更加方便消费者消费。

消费者消费吃火腿肠的同时,生产者可能在放假;生产者在生产火腿肠的同时,消费者可能又没有在吃火腿肠。这种行为用计算机术语来形容就是:生产者和消费者实现了解耦。解耦就是互相不干扰的意思。超市在计算机体系中就是共享资源--->消费者需要通过访问超市来获取商品,生产者需要通过访问超市来销售商品。在超市中类似超市这样的作用的区域我们称之为缓冲区。

我们刚刚说:生产者消费者模型实现了生产和消费的解耦。那在我们写代码的过程中,有没有强耦合的代码呢?有的。

最显著的例子就是函数调用。我们认为的函数的过程一般是这个样子的:首先实参通过形参传递给函数,然后经过函数体内部复杂的运算,输出运输的结果。

  • 调用方:生产了数据。
  • 形参变量:暂时保存数据。‘
  • 目标函数:消费了数据。

假如,我们代码中main函数中调用的func函数。我们把数据传给func函数的时候,main函数在做什么?它什么都做不了,只能等待调用func函数结束。这就是强耦合关系的例子。 


 🚀再次理解生产消费模型

我们依旧使用上面的超市的模型来深度挖掘生产消费模型的特点。生产者对应一个或者多个线程。消费者对应一个或者多个线程。

①生产者和生产者之间是什么关系?

这个大家都知道,一定是互斥即竞争关系。俗话说同行是冤家,在超市展柜上展出自己的商品时,只能同一个品牌上完货,然后另一个火腿肠品牌再上货。负责生产数据的线程之间的关系也是如此。

②消费和消费者之间是什么关系呢?

试想一下:假如过几天就是世界末日,你和同学两个人去超市买火腿肠,但是火腿肠只有一根了,你们两个肯定因为这根火腿肠而吵起来。所以消费者和消费者之间同样是互斥关系

③生产者和消费者之间是什么关系呢?

假如有一次,你去超市买火腿肠,同时超市的工作人员或者火腿肠的厂家正在上货。你们两个此时比较尴尬,是先上货呢?还是先让我拿呢?是可以沟通的。但是对于计算机而言却不是这样的,操作系统内的线程确实无法沟通的。假如在操作系统内有一块空间,一个线程正在读取这块空间中的内容,与此同时,一个线程正在修改这块空间里的内容,毫无疑问读取的内容一定发生了改变。这是不合理的,所以生产者和消费者之间首先要保持着互斥的关系,不让其同时访问

假如有这么一个节日,节日期间流行吃火腿肠,所以超市里的供不应求。张三同学为了吃上火腿肠,每天准时准点来到超市问工作人员火腿肠到了没有,假如每天会有很多人前来询问,这对这位工作人员来说,也是一种负担。所以这位工作人员就要求加张三的维信,有火腿肠了就通知张三,这样张三就不用每天来到这里询问了。假如有这样一段时间,是火腿肠的淡季,超市里堆积了大量的火腿肠,但是工厂还源源不断的生产着,所以厂家每天来超市询问是否要进货。这也让超市的工作人员非常讨厌。一天,这位工作人员也加上了厂家的微信,如果需要进货,就给厂家发消息。所以这样,就间接维护了生产者和消费者的同步关系

总结起来,生产消费模型要遵守321的原则。只要我们想写生产者消费者模型,我们的本质是要维护321原则。

3种关系

生产者和生产者要保持互斥关系②消费者和消费者之间要保持互斥关系③消费者和生产者之间既要保持互斥关系也要保持同步关系。

2种角色

生产者线程,消费者线程

1个交易场所

一段特定结构的缓冲区 

 🚀挖掘特点

①生产线程和消费线程进行解耦

②支持生产者和消费者一段时间的忙闲不均的问题

有没有可能在一段时间内,生产者的生产能力很强,但是消费者的消费水平很低;或者生产者的生产水平很弱,但是消费者的消费水平很强。但是由于中间的超市的存在,可以平衡生产者和消费者之间生产和消费的问题。在我们的计算机内部,也是如此的

③提高效率

在社会发展中,出现了超市这样的事物,一定有它存在的道理和意义。假如没有超市的存在,我们需要购买商品时,就需要跑到工厂,相比于在超市中购买,肯定要浪费更多的时间。而工厂的工作人员就需要抽出人力来销售自己生产的商品,也浪费人力资源。所以生产者消费者模型的出现,可以提高我们的效率。在我们的计算机内部也是如此,同样适用。关于提高效率这块的内容,我们后边还会说明。


所以,我们现在是不是有能力将函数调用解耦呢?

是的,现在我们就可以实现函数调用解耦了。具体我们可以这样做:

我们先定义一个缓冲区,负责存储实参。我们先将要喂给调用函数作为实参的数据存储在缓冲区内,然后调用函数可以随时从缓冲区内读取数据,作为实参进行处理,然后输出结果。这样两个执行流就由串行执行变为并发执行,真正意义上实现了解耦。


但我们忽略了一个问题:生产者和消费者的关系是互斥之间的关系,就是同一时间,仅允生产线程和消费线程中的一个线程访问缓冲区(也就是临界区)。假如生产者的优先级非常高,同时缓冲区的数据已满,不允许再写入数据,但是生产线程却不断的进行查询,这样也就会导致一个线程一直访问临界资源,就会造成我们刚刚说的自习室问题。具体如图:

如上图,结合这份伪代码,大家应该可以理解。这时,我们就要提出条件变量的概念了。

 🚩条件变量

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。这句话应该如何理解呢?

如上图,若干个线程互斥性的访问ticket这个变量,但只有剩余票数大于0时,才能完成抢票的动作,否则什么也干不了。此时如果一个线程的优先级很高,那么它就会不停的查询ticket的值,造成一定程度上的无用查询。这时我们就可以设定一个条件变量,等到ticket大于0时,通知该线程来抢票,不用一直在这里查询了。这就是条件变量的用处。

🚀条件变量常用接口

// 所有条件变量的相关函数都在该头文件下
#include <pthread.h>
// 创建一个条件变量
pthread_cond_t +变量名
// 销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
// 对一个条件变量进行初始化,参数:cond:要初始化的条件,attr:NULL
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
// 如果这个条件变量是静态的或者全局的,也可以这样初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
// 等待条件满足,参数:cond:要在这个条件变量上等待,mutex:互斥量,后面详细解释
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
// 如下两个函数是唤醒等待
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

注意:这些函数如果有返回值,默认成功的话,返回0;失败的话,错误码被设置。

接下来,见见猪跑

#include <stdio.h>
#include <stdlib.h> 
#include <string.h>
#include <unistd.h>
#include <pthread.h>
pthread_cond_t cond;
pthread_mutex_t mutex;
void *r1(void *arg)
{while (1){pthread_cond_wait(&cond, &mutex);printf("活动\n");}
}
void *r2(void *arg)
{while (1){pthread_cond_signal(&cond);sleep(1);}
}
int main(void)
{pthread_t t1, t2;pthread_cond_init(&cond, NULL);pthread_mutex_init(&mutex, NULL);pthread_create(&t1, NULL, r1, NULL);pthread_create(&t2, NULL, r2, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);
}

 🚀条件变量的原理

接下来,我们讲一个故事

一到招聘季,互联网公司总是派面试官以出差的形式去全国各地招收优秀的人才。他们每到一处地方,一般都会包下一个宾馆的一层楼,作为他们的面试场所。假设他们来到了济南,然后把面试场所安排在了万达酒店。

在一间屋子里,面试官正面试着一位同学,不一会儿面完了。此时门口挤满了求职者,他们都高举着自己的简历,然后高喊:"我先来的,应该先面我"。无奈,面试官只能挑喊的声音大的先面试。等到下午,吸取了经验,面试官在门口划定了一块区域,说:“只从这里选人面试,并且排队”

 

 其中,这块区域就像条件变量。

当条件不满足时,我们线程必须去某些定义好的条件变量下等待。

说到这里,本篇内容就结束了,我们下期博客再见!

相关文章:

  • MISRA C 和MISRA C++:汽车软件安全的守护者
  • redis-benchmark 使用
  • 使用SSE实现echarts数据实时更新
  • c++静态成员变量和静态成员函数
  • 腾讯云函数部署环境[使用函数URL]
  • 华为OD机考题(HJ50 四则运算)
  • WCCI 2024第三弹:忍者表演惊艳全场,盛大晚宴不容错过
  • 视频监控业务平台LntonCVS国标视频综合管理平台功能及技术优势
  • 暑期编程预习指南
  • CentOS 7镜像列表服务下线,还想继续使用该怎么办?
  • MySQL数据库增删改查示例
  • 量化交易心法——如何建立自己的算法交易事业
  • html+css+js淘宝商品界面
  • 析构函数和拷贝构造函数
  • 拼多多职位数据信息采集
  • 【翻译】babel对TC39装饰器草案的实现
  • 2017-09-12 前端日报
  • 30秒的PHP代码片段(1)数组 - Array
  • chrome扩展demo1-小时钟
  • GitUp, 你不可错过的秀外慧中的git工具
  • JavaScript设计模式系列一:工厂模式
  • Redis 懒删除(lazy free)简史
  • vue和cordova项目整合打包,并实现vue调用android的相机的demo
  • 仿天猫超市收藏抛物线动画工具库
  • 官方解决所有 npm 全局安装权限问题
  • 机器学习 vs. 深度学习
  • 聚类分析——Kmeans
  • 如何编写一个可升级的智能合约
  • 少走弯路,给Java 1~5 年程序员的建议
  • 什么软件可以剪辑音乐?
  • 数组大概知多少
  • 限制Java线程池运行线程以及等待线程数量的策略
  • # Apache SeaTunnel 究竟是什么?
  • #13 yum、编译安装与sed命令的使用
  • #控制台大学课堂点名问题_课堂随机点名
  • #微信小程序(布局、渲染层基础知识)
  • (06)金属布线——为半导体注入生命的连接
  • (2.2w字)前端单元测试之Jest详解篇
  • (3)医疗图像处理:MRI磁共振成像-快速采集--(杨正汉)
  • (ibm)Java 语言的 XPath API
  • (笔试题)合法字符串
  • (二刷)代码随想录第15天|层序遍历 226.翻转二叉树 101.对称二叉树2
  • (没学懂,待填坑)【动态规划】数位动态规划
  • (亲测成功)在centos7.5上安装kvm,通过VNC远程连接并创建多台ubuntu虚拟机(ubuntu server版本)...
  • (新)网络工程师考点串讲与真题详解
  • (一一四)第九章编程练习
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • (转)关于如何学好游戏3D引擎编程的一些经验
  • (轉貼) VS2005 快捷键 (初級) (.NET) (Visual Studio)
  • ***检测工具之RKHunter AIDE
  • .NET C# 使用GDAL读取FileGDB要素类
  • .Net FrameWork总结
  • .NET Standard 支持的 .NET Framework 和 .NET Core
  • .NET 依赖注入和配置系统
  • .NET6 开发一个检查某些状态持续多长时间的类