《Linux从练气到飞升》No.28 Linux中的线程同步
🕺作者: 主页
我的专栏 C语言从0到1 探秘C++ 数据结构从0到1 探秘Linux 菜鸟刷题集 😘欢迎关注:👍点赞🙌收藏✍️留言
🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的很重要,有问题可在评论区提出,感谢阅读!!!
文章目录
- 前言
- 1 相关概念
- 1.1 条件变量
- 1.2 同步概念与竞态条件
- 1.3 条件变量函数
- 2 实际应用(见见猪跑
- 2.1 模拟加锁未加条件变量(小迷给小芒煮饭且只有一个碗
- 2.2 模拟加锁且加上条件变量
- 2.3 模拟加锁且加条件变量(小迷给多个人做饭 只有一个碗
- 3 条件变量关于等待接口的几个问题
- 3.1 条件变量对的等待接口参数为什么需要互斥锁?
- 3.2 pthread_cond_wait函数的实现原理
- 3.3 线程等待的时候,被唤醒之后需要做什么事?
前言
当谈到多线程编程时,线程同步是一个至关重要的话题。在多线程环境中,我们需要确保不同线程之间的数据访问和操作能够正确、有序地进行,以避免出现竞争条件和数据不一致的情况。因此,线程同步成为了保障多线程程序正确性和可靠性的重要手段。
在本篇博客中,我将深入探讨线程同步的概念、原理和常用的同步机制,帮助读者更好地理解多线程编程中的挑战和解决方案。无论是初学者还是有一定经验的开发人员,都可以通过本文获得对线程同步的全面了解,并学习如何在实际项目中应用这些技术来确保多线程程序的稳定性和性能。
让我们一起深入研究线程同步,探索其中的奥秘,为多线程编程的世界增添一抹精彩的色彩。
1 相关概念
1.1 条件变量
- 当一个线程互斥的访问某个变量时,它可能发现在其他线程改变状态之前,它什么也做不了
- 例如一个线程访问队列时,发现队列为空,它只能等待,只到其他线程将一个节点添加到队列中,这种情况就需要用到条件变量
1.2 同步概念与竞态条件
- 同步:在保证数据安全的前提下,让线程能够按照某种特定顺序访问临界资源,从而有效避免饥饿问题,这叫做同步。
- 竞态条件:因为时序问题,而导致程序异常。我们称之为竞态条件。在线程场景下,这种问题也不难理解
1.3 条件变量函数
- 初始化
动态初始化:
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrictattr);🔄 ❓
参数:cond:要初始化的条件变量attr:NULL
静态初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
- 销毁
int pthread_cond_destroy(pthread_cond_t *cond)
- 等待条件满足
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:cond:要在这个条件变量上等待mutex:互斥量,之前的博客解释过
作用:谁调用该接口,就将谁放入PCB等待队列中
- 唤醒等待
唤醒PCB等待队列当中的所有线程:int pthread_cond_broadcast(pthread_cond_t *cond);
唤醒PCB等待队列当中至少一个线程:int pthread_cond_signal(pthread_cond_t *cond);
2 实际应用(见见猪跑
2.1 模拟加锁未加条件变量(小迷给小芒煮饭且只有一个碗
代码如下:
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;int g_bowl=1;
pthread_mutex_t g_lock;void* Eat(void* arg){(void)arg;while(1){pthread_mutex_lock(&g_lock);g_bowl--;cout<<"I am "<<pthread_self()<<"I eat "<<g_bowl<<endl;pthread_mutex_unlock(&g_lock);}return NULL;
}void* MakeRice(void* arg){(void)arg;while(1){pthread_mutex_lock(&g_lock);g_bowl++;cout<<"I am "<<pthread_self()<<"I make "<<g_bowl<<endl;pthread_mutex_unlock(&g_lock);}return NULL;
}int main(){pthread_mutex_init(&g_lock,NULL);pthread_t tid_eat;pthread_t tid_make;int ret = pthread_create(&tid_eat,NULL,Eat,NULL);if(ret<0){cout<<"thread create failed"<<endl;}ret = pthread_create(&tid_make,NULL,MakeRice,NULL);if(ret < 0){cout<<"thread create failed"<<endl;}while(1){sleep(1);}pthread_mutex_destroy(&g_lock);return 0;
}
结果:

可以观察到bowl已经减为负数,这是因为小芒负责吃,当小芒拿到CPU的资源时,即使碗里面没有饭,它还是持续吃饭,最后居然出现了没有饭还能吃饭的情况,这显然是不合理的所以需要一个条件变量来控制能否吃,以及能否做
2.2 模拟加锁且加上条件变量
给小迷加上条件变量,bowl 里面有饭就不做饭,给小芒加上条件变量,bowl 没有饭就不吃饭。
代码如下:
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;int g_bowl=1;
pthread_mutex_t g_lock;pthread_cond_t g_eat_cond;
pthread_cond_t g_make_cond;void* Eat(void* arg){(void)arg;while(1){pthread_mutex_lock(&g_lock);if(g_bowl<=0){pthread_cond_wait(&g_eat_cond,&g_lock);//等待小迷做好饭}g_bowl--;cout<<"I am "<<pthread_self()<<"I eat "<<g_bowl<<endl;pthread_mutex_unlock(&g_lock);pthread_cond_signal(&g_make_cond);//通知小迷做饭}return NULL;
}void* MakeRice(void* arg){(void)arg;while(1){pthread_mutex_lock(&g_lock);if(g_bowl>=1){pthread_cond_wait(&g_make_cond,&g_lock);//等待小芒吃饭 空出碗}g_bowl++;cout<<"I am "<<pthread_self()<<"I make "<<g_bowl<<endl;pthread_mutex_unlock(&g_lock);pthread_cond_signal(&g_eat_cond);//通知小芒吃饭}return NULL;
}int main(){pthread_mutex_init(&g_lock,NULL);pthread_cond_init(&g_eat_cond,NULL);pthread_cond_init(&g_make_cond,NULL);pthread_t tid_eat;pthread_t tid_make;int ret = pthread_create(&tid_eat,NULL,Eat,NULL);if(ret<0){cout<<"thread create failed"<<endl;}ret = pthread_create(&tid_make,NULL,MakeRice,NULL);if(ret < 0){cout<<"thread create failed"<<endl;}while(1){sleep(1);}pthread_mutex_destroy(&g_lock);pthread_cond_destroy(&g_eat_cond);pthread_cond_destroy(&g_make_cond);return 0;
}
结果如下:

2.3 模拟加锁且加条件变量(小迷给多个人做饭 只有一个碗
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;int g_bowl=1;
pthread_mutex_t g_lock;pthread_cond_t g_eat_cond;
pthread_cond_t g_make_cond;void* Eat(void* arg){(void)arg;while(1){pthread_mutex_lock(&g_lock);if(g_bowl<=0){pthread_cond_wait(&g_eat_cond,&g_lock);}g_bowl--;cout<<"I am "<<pthread_self()<<"I eat "<<g_bowl<<endl;pthread_mutex_unlock(&g_lock);pthread_cond_signal(&g_make_cond);}return NULL;
}void* MakeRice(void* arg){(void)arg;while(1){pthread_mutex_lock(&g_lock);if(g_bowl>0){pthread_cond_wait(&g_make_cond,&g_lock);}g_bowl++;cout<<"I am "<<pthread_self()<<"I make "<<g_bowl<<endl;pthread_mutex_unlock(&g_lock);pthread_cond_signal(&g_eat_cond);}return NULL;
}int main(){pthread_mutex_init(&g_lock,NULL);pthread_cond_init(&g_eat_cond,NULL);pthread_cond_init(&g_make_cond,NULL);pthread_t tid_make;int ret = pthread_create(&tid_make,NULL,MakeRice,NULL);if(ret < 0){cout<<"thread create failed"<<endl;}for(int i=0;i<3;++i){pthread_t tid_eat;int ret = pthread_create(&tid_eat,NULL,Eat,NULL);if(ret<0){cout<<"thread create failed"<<endl;}}while(1){sleep(1);}pthread_mutex_destroy(&g_lock);pthread_cond_destroy(&g_eat_cond);pthread_cond_destroy(&g_make_cond);return 0;
}
结果:

可以看到出现了负数的情况,这是为什么?
这是因为我们是使用if语句来判断条件的,可能线程刚好在这个时候进行了切换,导致多个eat线程拿到了锁,从而发生了这样的现象,想要解决这个问题只需要改为while语句即可
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;int g_bowl=1;
pthread_mutex_t g_lock;pthread_cond_t g_eat_cond;
pthread_cond_t g_make_cond;void* Eat(void* arg){(void)arg;while(1){pthread_mutex_lock(&g_lock);while(g_bowl<=0){pthread_cond_wait(&g_eat_cond,&g_lock);}g_bowl--;cout<<"I am "<<pthread_self()<<" I eat "<<g_bowl<<endl;pthread_mutex_unlock(&g_lock);pthread_cond_signal(&g_make_cond);}return NULL;
}void* MakeRice(void* arg){(void)arg;while(1){pthread_mutex_lock(&g_lock);while(g_bowl>0){pthread_cond_wait(&g_make_cond,&g_lock);}g_bowl++;cout<<"I am "<<pthread_self()<<" I make "<<g_bowl<<endl;pthread_mutex_unlock(&g_lock);pthread_cond_signal(&g_eat_cond);}return NULL;
}int main(){pthread_mutex_init(&g_lock,NULL);pthread_cond_init(&g_eat_cond,NULL);pthread_cond_init(&g_make_cond,NULL);pthread_t tid_make;int ret = pthread_create(&tid_make,NULL,MakeRice,NULL);if(ret < 0){cout<<"thread create failed"<<endl;}for(int i=0;i<3;++i){pthread_t tid_eat;int ret = pthread_create(&tid_eat,NULL,Eat,NULL);if(ret<0){cout<<"thread create failed"<<endl;}}while(1){sleep(1);}pthread_mutex_destroy(&g_lock);pthread_cond_destroy(&g_eat_cond);pthread_cond_destroy(&g_make_cond);return 0;
}
结果:

3 条件变量关于等待接口的几个问题
3.1 条件变量对的等待接口参数为什么需要互斥锁?
在pthread_cond_wait函数的内部,需要释放互斥锁。释放之后,其他线程就可以正常加锁操作了。
eg:就像之前小芒发现碗里面没有饭,则需要将自己放到PCB等待队列中,调用了pthread_cond_wait函数之后,需要将拿到互斥锁释放掉,小迷就可以访问到碗这个临界资源开始做饭。
3.2 pthread_cond_wait函数的实现原理
在pthread_cond_wait函数内部,是先释放互斥锁,还是先将PCB放到等待队列中呢?
假设先释放互斥锁,此时可能做饭的小迷就已经将饭做好了,但是小芒还没有到等待队列中,小迷通知小芒吃饭,但是发现等待队列中为空,但是同时发现碗里面有饭,它就会将自己放入等待队列中等待,此时小芒也才将自己放入等待队列中,那么此时小迷和小芒就都在等待队列中进行等待,所以不能先释放互斥锁。
3.3 线程等待的时候,被唤醒之后需要做什么事?
- 移动出PCB等待队列
- 抢互斥锁
- 抢到了:
pthread_cond_wait函数返回了 - 没抢到:
pthread_cond_wait函数没有返回,等待抢锁
- 抢到了:
相关文章:
《Linux从练气到飞升》No.28 Linux中的线程同步
🕺作者: 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 😘欢迎关注:👍点赞🙌收藏✍️留言 🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的…...
爬取动态网页内容的库
爬取动态网页内容时,传统的 Python 爬虫库(如 Requests、BeautifulSoup)可能无法直接获取 JavaScript 动态生成的内容。为了处理这种情况,你可以使用一些特别设计的库,它们能够模拟浏览器行为,执行 JavaScr…...
Ubuntu 安装常见问题
1. 安装oh my zsh 搜狗输入法不能用 vim /etc/environmentexport XIM_PROGRAMfcitx export XIMfcitx export GTK_IM_MODULEfcitx export QT_IM_MODULEfcitx export XMODIFIERS“imfcitx” export LANG“zh_CN.UTF-8”配置完后重启,稍等一会,右上角会有个…...
大数据分析师职业技能提升好考吗?含金量高不高
随着大数据时代的到来,大数据分析技能需求已经成为很多企业和机构的必备要求。大数据分析师证书成为当下的热门之一,那么大数据分析师证书需要具备哪些条件呢? 首先,报考大数据分析师证书需要具备以下方面的条件: …...
JumpServer2023漏洞复现合集
本文主要复现JumpServer2023年出现的大批量漏洞,既是分享也是为了记录自己的成长,近期会持续更新。 1. JumpServer MongoDB远程代码执行漏洞(CVE-2023-43651) 1.1 漏洞级别 高危 1.2 漏洞描述 经过身份验证的用户可以利用Mon…...
【Linux】Ubuntu16.04配置repo
Ubuntu16.04配置repo失败 在学习韦东山Linux嵌入式开发过程中,使用repo获取内核及工具链: git clone https://e.coding.net/codebug8/repo.gitmkdir -p 100ask_imx6ull-sdk && cd 100ask_imx6ull-sdk../repo/repo init -u https://gitee.com/weidongshan/m…...
uniapp小程序更新逻辑,按实际开发为主
小程序更新: uniapp小程序更新逻辑 uni.getUpdateManager() 方法参数说明onCheckForUpdatecallback当向小程序后台请求完新版本信息,会进行回调onUpdateReadycallback当新版本下载完成,会进行回调onUpdateFailedcallback当新版本下载失败,会…...
骨传导蓝牙耳机哪款好?这五款骨传导耳机闭眼入都不会错!
随着科技的发展,数码产品更新换代的速度也是越来越快,如今无线蓝牙耳机已经占据主流,特别是运动爱好者,很多人都会为自己挑选一款好用的运动耳机,而骨传导耳机异军突起,凭借听歌不入耳、佩戴舒适稳固等特性…...
数据库操作入门:PyMongo 和 MongoDB 的基本用法
MongoDB MongoDB是一种流行的NoSQL数据库,它将数据存储在类似JSON的文档中,使数据库非常灵活和可扩展 PyMongo Python需要一个MongoDB驱动程序来访问MongoDB数据库。在本教程中,我们将使用MongoDB驱动程序 “PyMongo”。建议使用PIP来安装…...
开发企业微信群机器人,实现定时提醒
大家好,我是鱼皮,今天分享一个用程序解决生活工作问题的真实案例。 说来惭愧,事情是这样的,在我们公司,每天都要轮流安排一名员工(当然也包括我)去楼层中间一个很牛的饮水机那里接水。但由于大…...
剑指 Offer 06. 从尾到头打印链表
title: 剑指 Offer 06. 从尾到头打印链表 tags: 链表递归迭代 categories:算法剑指 Offer 题目描述 输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。 示例 1: 输入:head [1,3,2] 输出&#…...
深度学习之基于Pytorch服装图像分类识别系统
欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介系统组成1. 数据集准备2. 数据预处理3. 模型构建4. 模型训练5. 模型评估 PyTorch的优势 二、功能三、系统四. 总结 一项目简介 深度学习在计算机视觉领域的…...
串口通讯:
一、 1.在用ReadFile和WriteFile读写串口时,既可以同步执行,也可以重叠执行: 在同步执行时,函数直到操作完成后才返回。这意味着同步执行时线程会被阻塞,从而导致效率下降。 在重叠执行时,即使操作…...
批量重命名软件推荐 A Better Finder Rename 12最新 for mac
A Better Finder Rename的大量重命名选项被组织成15个直观的类别,涵盖了一个伟大的文件重命名器所期望的所有文本,字符,位置,转换和截断功能。 除此之外,A Better Finder Rename提供了更多高级功能,可以满…...
【2013年数据结构真题】
highlight: a11y-dark 41题 王道解析: 算法的策略是从前向后扫描数组元素,标记出一个可能成为主元素的元素Num 。然后重新计数,确认Num是否是主元素。算法可分为以下两步: 选取候选的主元素:依次扫描所给数组中的每个…...
csrf学习笔记总结
跨站请求伪造csrf csrf概述 掌握CSRF 漏洞原理 掌握CSRF 漏洞场景 掌握CSRF 漏洞验证 csrf原理 跨站请求伪造(Cross Site Request Forgery,CSRF)是一种攻击,它强制浏览器客户端用户在当前对其进行身份验证后的Web 应用程…...
【kafka】windows安装启动
1.zookeeper的安装与启动 快速打开window powershell: windowx,选 2.kafka下载 —注意kafka和zookeeper需要版本匹配 安装路径 注意,kafka安装目录不能有空格。文件下载到: D:\Program_Files\kafka_2.12-3.6.0新建logs文件 修改c…...
redis的基本命令,并用netty操作redis(不使用springboot或者spring框架)就单纯的用netty搞。
大家如果对使用netty搞这些http请求什么的感兴趣的,可以参观我自己创建的这个项目。 nanshaws/nettyWeb: 复习一下netty,并打算做一个web项目出来 (github.com) Redis的基本命令包括: SET key value:设置指定key的值。 GET key…...
《白帽子讲web安全》笔记
第八章 文件上传漏洞 文件上传漏洞是指用户上传了一个可执行的脚本文件,并通过此脚本文件获得了执行服务器端命令的能力 文件上传后导致的常见安全问题一般有: ❍ 上传文件是Web脚本语言,服务器的Web容器解释并执行了用户上传的脚本…...
unity UGUI无限循环滚动居中
最近在做一个ui循环滚动的功能,网上找了半天脚本感觉都和我实际需求不太符合,自己花费一些时间完成了这个功能记录一下。下面开始正题 ,我是采用unity自带组件Scroll View来完成,首先设置Scroll View如下图 面板层级结构如下 然…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
