《OSTEP》条件变量(chap30)
〇、前言
本文是对《OSTEP》第三十章的实践与总结。
一、条件变量
#include <pthread.h>
#include <stdio.h>
#include <assert.h>int buffer;
int count = 0; // 资源为空// 生产,在 buffer 中放入一个值
void put(int value) {assert(count == 0);count = 1;buffer = value;
}
// 消费,取出 buffer 中的值
int get() {assert(count == 1);count = 0;return buffer;
}/***********第一版本**********/
// 生产者
void *producer(void *arg) {int loops = *((int *)arg);for (int i = 0; i < loops; i++) {put(i);}return NULL;
}
// 消费者
void *consumer(void *arg) {while (1) {int temp = get();printf("消费的值:%d\n", temp);}return NULL;
}int main() {pthread_t p1, p2;int arg = 100;pthread_create(&p1, NULL, producer, &arg);pthread_create(&p2, NULL, consumer, NULL);// 等待两个线程结束pthread_join(p1, NULL);pthread_join(p2, NULL);return 0;
}
运行:
*** chap30_条件变量 % gcc -o a con_prodece.c
*** chap30_条件变量 % ./a
Assertion failed: (count == 0), function put, file con_prodece.c, line 10.
消费的值:0
Assertion failed: (count == 1), function get, file con_prodece.c, line 16.
zsh: abort ./a
可以看到,断言直接失败。
pthread_cond_t cond;
pthread_mutex_t mutex;
/***********第二版本**********/
// 生产者
void *producer(void *arg) {int loops = *((int *)arg);for (int i = 0; i < loops; i++) {pthread_mutex_lock(&mutex);if (count == 1) {pthread_cond_wait(&cond, &mutex);}put(i);pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond);}return NULL;
}
// 消费者
void *consumer(void *arg) {int loops = *((int *)arg);for (int i = 0; i < loops; i++) {pthread_mutex_lock(&mutex);if (count == 0) {pthread_cond_wait(&cond, &mutex);}int temp = get();pthread_mutex_unlock(&mutex);printf("消费:%d\n", temp);pthread_cond_signal(&cond); }return NULL;
}int main() {// 初始化互斥锁和条件变量pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond, NULL);pthread_t p1, p2;int arg = 100;pthread_create(&p1, NULL, producer, &arg);pthread_create(&p2, NULL, consumer, &arg);// 等待两个线程结束pthread_join(p1, NULL);pthread_join(p2, NULL);// 销毁互斥锁和条件变量pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}
运行结果:
*** chap30_条件变量 % ./a
消费:0
消费:1
消费:2
消费:3
...
消费:95
消费:96
消费:97
消费:98
消费:99
可以看到,在两个线程的情况下,工作的很好。
/***********第三版本**********/
// 生产者
void *producer(void *arg) {int loops = *((int *)arg);for (int i = 0; i < loops; i++) {pthread_mutex_lock(&mutex);if (count == 1) {pthread_cond_wait(&cond, &mutex);}put(i);pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond);}return NULL;
}
// 消费者
void *consumer(void *arg) {int loops = *((int *)arg);for (int i = 0; i < loops; i++) {pthread_mutex_lock(&mutex);if (count == 0) {pthread_cond_wait(&cond, &mutex);}int temp = get();pthread_mutex_unlock(&mutex);printf("消费:%d\n", temp);pthread_cond_signal(&cond); }return NULL;
}int main() {// 初始化互斥锁和条件变量pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond, NULL);pthread_t p1, p2, p3;int arg = 100;int arg1 = 50;int arg2 = 50;pthread_create(&p1, NULL, producer, &arg);pthread_create(&p2, NULL, consumer, &arg1);pthread_create(&p3, NULL, consumer, &arg2);// 等待两个线程结束pthread_join(p1, NULL);pthread_join(p2, NULL);pthread_join(p3, NULL);// 销毁互斥锁和条件变量pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}
运行结果:
*** chap30_条件变量 % gcc -o a con_prodece2.c
*** chap30_条件变量 % ./a
消费:0
消费:1
Assertion failed: (count == 1), function get, file con_prodece2.c, line 18.
zsh: abort ./a
可以看到,再增加了一个消费线程之后,出现了断言错误。这是因为出现了假唤醒,使得某个线程醒来后,断言错误。解决方法很简单,直接将 if()换成 while():
/***********第三版本**********/
// 解决虚假唤醒
// 生产者
void *producer(void *arg) {int loops = *((int *)arg);for (int i = 0; i < loops; i++) {pthread_mutex_lock(&mutex);while (count == 1) {pthread_cond_wait(&cond, &mutex);}put(i);pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond);}return NULL;
}
// 消费者
void *consumer(void *arg) {int loops = *((int *)arg);for (int i = 0; i < loops; i++) {pthread_mutex_lock(&mutex);while (count == 0) {pthread_cond_wait(&cond, &mutex);}int temp = get();pthread_mutex_unlock(&mutex);printf("消费:%d\n", temp);pthread_cond_signal(&cond); // 在解锁之后发出信号}return NULL;
}int main() {// 初始化互斥锁和条件变量pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond, NULL);pthread_t p1, p2, p3;int arg = 100;int arg1 = 50;int arg2 = 50;pthread_create(&p1, NULL, producer, &arg);pthread_create(&p2, NULL, consumer, &arg1);pthread_create(&p3, NULL, consumer, &arg2);// 等待两个线程结束pthread_join(p1, NULL);pthread_join(p2, NULL);pthread_join(p3, NULL);// 销毁互斥锁和条件变量pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}
运行结果:
luliang@shenjian chap30_条件变量 % ./a
消费:0
消费:2
消费:1
消费:3
消费:4
消费:5
...
可以看到会卡住,这其实是由于三个线程都睡眠导致的,这种情况是怎么发生的呢?
假设生产者唤醒了第一个消费者,消费者又恰巧唤醒了第二个生产者,第二个生产者被唤醒之后又睡眠。这样三个线程都在睡眠。解决问题的办法就是消费者只能唤醒生产者,生产者只能唤醒消费者。以下就是终极版本的代码:
#include <assert.h>
#include <pthread.h>
#include <stdio.h>int buffer;
int count = 0; // 资源为空
pthread_cond_t cond_consumer;
pthread_cond_t cond_procedure;
pthread_mutex_t mutex;// 生产,在 buffer 中放入一个值
void put(int value) {assert(count == 0);count = 1;buffer = value;
}
// 消费,取出 buffer 中的值
int get() {assert(count == 1);count = 0;return buffer;
}/***********第四版本**********/
// 假设生产者唤醒了第一个消费者,消费者又唤醒了第二个生产者,第二个生产者
// 之后又睡眠.这样三个线程都在睡眠.
// 核心问题就是,消费者只能唤醒生产者,生产者只能唤醒消费者.
// 生产者
void *producer(void *arg) {int loops = *((int *)arg);for (int i = 0; i < loops; i++) {pthread_mutex_lock(&mutex);while (count == 1) {pthread_cond_wait(&cond_procedure, &mutex);}put(i);pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond_consumer);}return NULL;
}
// 消费者
void *consumer(void *arg) {int loops = *((int *)arg);for (int i = 0; i < loops; i++) {pthread_mutex_lock(&mutex);while (count == 0) {pthread_cond_wait(&cond_consumer, &mutex);}int temp = get();pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond_procedure);printf("消费:%d\n", temp);}return NULL;
}int main() {// 初始化互斥锁和条件变量pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond_consumer, NULL);pthread_cond_init(&cond_procedure, NULL);pthread_t p1, p2, p3;int arg = 100;int arg1 = 50;int arg2 = 50;pthread_create(&p1, NULL, producer, &arg);pthread_create(&p2, NULL, consumer, &arg1);pthread_create(&p3, NULL, consumer, &arg2);// 等待线程结束pthread_join(p1, NULL);pthread_join(p2, NULL);pthread_join(p3, NULL);// 销毁互斥锁和条件变量pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond_consumer);pthread_cond_destroy(&cond_procedure);return 0;
}
运行结果:
*** chap30_条件变量 % gcc -o a con_prodece3.c
*** chap30_条件变量 % ./a
消费:0
消费:2
消费:1
...
消费:97
消费:98
消费:99
可以看到运行得很好,成功地解决了并发、虚假唤醒以及全部线程都睡眠的情况。
二、总结
我们看到了引入锁之外的另一个重要同步原语:条件变量。当某些程序状态不符合要求时,通过允许线程进入休眠状态,条件变量使我们能够漂亮地解决许多重要的同步问题,包括著名的(仍然重要的)生产者/消费者问题,以及覆盖条件。
相关文章:
《OSTEP》条件变量(chap30)
〇、前言 本文是对《OSTEP》第三十章的实践与总结。 一、条件变量 #include <pthread.h> #include <stdio.h> #include <assert.h>int buffer; int count 0; // 资源为空// 生产,在 buffer 中放入一个值 void put(int value) {assert(count 0);count 1…...
MySQL的索引和复合索引
由于MySQL自动将主键加入到二级索引(自行建立的index)里,所以当select的是主键或二级索引就会很快,select *就会慢。因为有些列是没在索引里的 假设CA有1kw人咋整,那我这个索引只起了前一半作用。 所以用复合索引&am…...
关于mac下pycharm旧版本没删除的情况下新版本2023安装之后闪退
先说结论,我用的app cleaner 重新删除的pycharm ,再重新安装即可。在此记录一下 之前安装的旧版的2020的pycharm,因为装不了新的插件,没办法就升级了。新装2023打开之后闪退,重启系统也不行,怀疑是一起破解…...
Django中如何让DRF的接口针对前后台返回不同的字段
在Django中,使用Django Rest Framework(DRF)时,可以通过序列化器(Serializer)和视图(View)的组合来实现前后台返回不同的字段。这通常是因为前后台对数据的需求不同,或者…...
【机器学习】Kmeans聚类算法
一、聚类简介 Clustering (聚类)是常见的unsupervised learning (无监督学习)方法,简单地说就是把相似的数据样本分到一组(簇),聚类的过程,我们并不清楚某一类是什么(通常无标签信息)࿰…...
getid3 获取视频时长
1、首先,我们需要先下载一份PHP类—getid3https://codeload.github.com/JamesHeinrich/getID3/zip/master 2.我在laravel6.0 中使用 需要在composer.json 自动加载 否则系统访问不到 在命令行 执行 composer dump-autoload $getID3 new \getID3();//视频文件需要放…...
如何知道一个程序为哪些信号注册了哪些信号处理函数?
https://unix.stackexchange.com/questions/379694/is-there-a-way-to-know-if-signals-are-present-in-your-application-and-which-sign 使用 strace...
34 mysql limit 的实现
前言 这里来看一下 我们常见的 mysql 分页的 limit 的相的处理 这个问题的主要是来自于 之前有一个需要处理 大数据量的数据表的信息, 将数据转移到 es 中 然后就是用了最简单的 “select * from tz_test limit $pageOffset, $pageSize ” 来分页处理 但是由于 数据表的数…...
jbase实现申明式事务
对有反射的语言,申明式事务肯定不可少。没必要没个人都try,catch写事务,写的不好的话还经常容易锁表,为此给框架引入申明式事务。申明式既字面意思,在需要事务的方法前面加一个申明,那么框架保证事务。 首…...
如何在在线Excel文档中规范单元格输入
在日常的工作中,我们常常需要处理大量的数据。为了确保数据的准确性和可靠性。我们需要对输入的数据进行规范化和验证。其中一个重要的方面是规范单元格输入。而数据验证作为Excel中一种非常实用的功能,它可以帮助用户规范单元格的输入,从而提…...
力扣138:随机链表的复制
力扣138:随机链表的复制 题目描述: 给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成ÿ…...
C语言左移与右移学习
在学习左移与右移之前,我们首先要学习两种移位运算:逻辑移位和算数移位。 逻辑位移:移出去的位丢弃,空缺位用0补充。 算数位移:移出去的位丢弃,空缺位用符号位补充。 左移 左移是高位溢出,低…...
asp.net core mvc之 视图
一、在控制器中找到匹配视图,然后渲染成 HTML 代码返回给用户 public class HomeController : Controller {public IActionResult Index(){return View(); //默认找 Views/Home/Index.cshtml ,呈现给用户} } 二、指定视图 1、控制器 publ…...
ChatGLM3 tool_registry.py 代码解析
ChatGLM3 tool_registry.py 代码解析 0. 背景1. tool_registry.py 0. 背景 学习 ChatGLM3 的项目内容,过程中使用 AI 代码工具,对代码进行解释,帮助自己快速理解代码。这篇文章记录 ChatGLM3 tool_registry.py 的代码解析内容。 1. tool_re…...
js实现定时刷新,并设置定时器上限
定时器 在js中,有两种定时器: 倒计时定时器 倒计时定时器,也叫延时定时器或一次性定时器 功能:倒计时多长时间后执行某个动作 语法:setTimeout(function, timeout); 返回值:int类型,当前定时器…...
常用Linux命令
df -h #查看磁盘 kill -9 pid #强制关闭程序 ifconfig #查看网卡信息 last …...
【C++】获取指定点所在屏幕的尺寸
问题 多个显示器时,获取指定点所在的显示器的尺寸。 分析 之前整理过获取屏幕尺寸的方法:https://blog.csdn.net/m0_43605481/article/details/125024500多显示器时,需要用到GetSystemMetrics、EnumDisplayDevices、EnumDisplaySettings函…...
软文发布如何选择对应的媒体
企业做软文推广第一步,就是选择合适的媒体进行投放,然而许多企业不知道如何选择合适的媒体导致推广工作十分被动,无法取得效果,今天媒介盒子就来和大家分享,企业应该如何选择对应的媒体。 一、 媒体类型 根据软文类型…...
Django如何创建表关系,Django的请求声明周期流程图
【1】表与表之间的关系 一对一 左表的一条记录对应右表的一条记录,反之亦然 多对一 左表的一条记录对应右表的多条记录,反之不成立 多对多 左表的一条记录对应右表的多表记录,反之成立 【2】django中创建表关系 class Book(models.Model):t…...
微服务-我对Spring Clound的理解
官网:https://spring.io/projects/spring-cloud 官方说法:Spring Cloud 为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理、服务发现、熔断器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话…...
从面试官视角看嵌入式C/C++:那些年我们踩过的坑与避开的雷
嵌入式C/C面试官的深度思考:技术考察背后的逻辑与实战智慧 在嵌入式开发领域,技术面试往往是一场无声的博弈。作为面试官,我们设计的每一个问题都像精心布置的棋盘,等待着候选人展示他们的思维路径。但这场博弈的目的不是难倒对方…...
从SD卡到EMMC:手把手教你用U-Boot的tftp和update_mmc命令完成系统引导迁移
从SD卡到EMMC:U-Boot引导迁移全流程实战指南 当开发板通过SD卡成功启动U-Boot后,如何将引导程序永久写入板载EMMC?这不仅关乎设备能否独立启动,更直接影响产品化部署的可靠性。本文将手把手带你完成从临时启动到永久固件部署的关键…...
LCD1602显示异常?51单片机驱动DS1302时钟的5个常见坑点及解决方法
51单片机驱动DS1302与LCD1602的五大实战陷阱与破解之道 1. 通信协议配置不当导致的显示异常 当LCD1602显示乱码或完全不亮时,首先需要检查通信协议配置。51单片机与LCD1602的通信需要严格遵循时序要求,常见问题包括: 初始化序列缺失ÿ…...
深入PyTorch源码:grid_sample的坐标映射到底是怎么算的?(从-1,1到像素索引)
深入PyTorch源码:grid_sample的坐标映射到底是怎么算的? 当你第一次使用grid_sample时,可能会被它神奇的坐标变换能力所吸引——它能够将归一化的[-1,1]坐标精确映射到输入特征图的像素索引上。但当你需要调试输出异常或优化性能时ÿ…...
从时钟树到中断回调:图解S32K3的STMPIT完整工作流程
从时钟树到中断回调:图解S32K3的STM&PIT完整工作流程 在汽车电子领域,精确的定时控制如同车辆的神经系统,协调着各个ECU的运作节奏。S32K3系列MCU作为NXP面向新一代汽车架构的核心控制器,其内置的STM(系统定时器模…...
【嵌入式Linux】---- 从设备树到应用层:基于PetaLinux与SDK的GPIO驱动全链路开发与调试
1. 嵌入式Linux开发环境搭建 第一次接触嵌入式Linux开发的朋友可能会被各种工具链和环境配置搞得晕头转向。我刚开始做Zynq平台开发时,光是搭建环境就折腾了好几天。现在回想起来,其实只要抓住几个关键步骤,整个过程就会顺利很多。 首先得准备…...
linuxdeployqt企业级应用部署:大规模分发与维护的最佳实践
linuxdeployqt企业级应用部署:大规模分发与维护的最佳实践 【免费下载链接】linuxdeployqt Makes Linux applications self-contained by copying in the libraries and plugins that the application uses, and optionally generates an AppImage. Can be used for…...
彻底解决Windows音量栏干扰的专业方案:HideVolumeOSD技术深度解析
彻底解决Windows音量栏干扰的专业方案:HideVolumeOSD技术深度解析 【免费下载链接】HideVolumeOSD Hide the Windows 10 volume bar 项目地址: https://gitcode.com/gh_mirrors/hi/HideVolumeOSD 在Windows 10/11系统中,音量控制条(OS…...
别再只用微信授权了!手把手教你用小程序云开发实现账号密码登录注册(附完整源码)
突破微信授权限制:小程序云开发构建完整账号体系实战指南 每次看到小程序弹出"微信授权登录"的界面时,你有没有想过——如果用户拒绝授权,你的应用就彻底失去了这个用户?去年我们团队就遇到过这样的尴尬:一个…...
BepInEx完全指南:3步让任何Unity游戏变身插件平台
BepInEx完全指南:3步让任何Unity游戏变身插件平台 【免费下载链接】BepInEx Unity / XNA game patcher and plugin framework 项目地址: https://gitcode.com/GitHub_Trending/be/BepInEx BepInEx是一个强大的游戏插件框架,专门为Unity Mono、IL2…...
