【Linux】线程同步和死锁
目录
死锁
什么是死锁
构成死锁的四个必要条件
如何避免死锁
线程同步
同步的引入
同步的方式
条件变量
条件变量的使用
整体代码
死锁
什么是死锁
死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放 的资源而处于的一种永久等待状态.
例如进程两个锁mtx1和mtx2,进程A执行某一段代码需要先申请mtx1,再申请mtx2;而进程B执行对应的代码需要现申请mtx2,再申请mtx1.
某个时刻,进程A和B同时运行,进程A拿到了mtx1,进程B拿到了mtx2,但紧接着,进程A需要mtx2,但此时这把锁被进程B所占用,无法申请,便阻塞等待进程B的完成。而进程B需要mtx1,但是此时被A占用,需要等待进程A的完成,也阻塞在了这里。于是便造成了死锁。

构成死锁的四个必要条件
- 互斥条件: 一个资源每次只能被一个执行流使用,其他进程无法同时访问该资源。
- 请求与保持条件:即进程在请求资源时可以保持已占有的资源,即不释放自己原本的资源
- 不剥夺条件:已经获得资源的进程不能被强行剥夺其他进程所拥有的资源,只有自愿释放
- 循环等待条件:若干进程之间形成一种头尾相连的循环等待资源关系。如A->B,B->A.
如何避免死锁
我们知道了构成死锁的那个四个必要条件,只要破坏其中任意一个 条件即可:
- 破坏互斥条件:尽量避免使用互斥资源,或者采用不同的资源访问方式,如读写锁,允许多个进程或线程同时访问某些资源。
- 破坏请求与保持条件:如果申请多个锁失败,则释放自己已经拥有的资源。
- 破坏不剥夺条件:引入资源抢占机制,即允许操作系统对进程已获得的资源进行抢占。当其他进程紧迫需要某个资源时,系统可以终止或暂停某个进程,将其持有的资源释放分配给需要的进程。
-
打破环路等待条件:采用全局资源排序策略,为每个资源指定一个唯一的编号,然后要求进程只能按照编号递增的顺序请求资源,这样可以避免环路等待的发生。
以上所说的"资源"都也可以理解为锁。
线程同步
同步的引入
上一章我们说的
1.多线程然后抢票的例子,我们发现虽然有多个线程,但是每一次基本上都是那一个线程在抢(比如优先级可能更高),其它线程抢不到,这就是一个线程频繁地申请到资源,造成别的线程饥饿问题。
2.假设一个资源暂时没有了,而线程依旧在竞争锁,然后访问资源,访问不到然后释放锁没就这样一直进行,但此时也没有资源可用。这样就太过于浪费了。
以上这些操作都是正确的,但是是不合理的!
所以为了解决上面这系列问题,便引入了同步:主要是为了解决 访问临界资源合理性问题的.
即按照一定的顺序,进行临界资源的访问。
1.对于问题一我们可以这样:当一个线程申请到资源后,使用完之后,排到其它线程后面,让其他线程先访问,如此进行下去。
2.对于问题二,我们可以暂时每个线程发个号,当有资源时,再按照号的顺序来访问资源,而不是互相不正当竞争这份资源
所以线程同步的是:线程同步是指在并发编程中,通过协调多个线程的执行顺序以及对共享资源的访问来保证线程之间的正确交互
同步的方式
条件变量
当我们申请临界资源时 ---> 要对临界资源是否存在做检测 ---> 检测的本质:也是访问临界资源 --->结论:对临界资源的检测,也一定是在加锁和解锁之间的。
既然这样,那检测依然需要频繁地申请和释放锁,那么有没有办法让线程检测到资源不就绪的时候:
a.不要让线程自己再频繁检测了,而是等待
b.当条件就绪的时候,通知对应的线程,让它来进行资源的申请与访问。
为了满足上面的说话,这里就有引入条件变量。
条件变量(Condition Variable)是一种同步原语,常用于多线程编程中进行线程间的等待和通知。它用于实现线程之间的同步和协作,使得一个线程可以等待某个条件的满足并被其他线程通知唤醒。
条件变量的使用
使用条件变量需要配合互斥锁(pthread_mutex_t)来保证线程的安全操作。一般的使用步骤如下:
首先我们要先创建一个条件变量,数据类型为 pthread_cond_t,同时也要创建互斥锁。
pthread_mutex_t mtx;pthread_cond_t cond;
初始化条件变量和互斥锁:互斥锁我们说了初始化方式了,这里说初始化条件变量的函数:
pthread_cond_init,该函数原型如下:
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
第一个参数为条件变量,第二个参数为条件变量属性,一般设为NULL。
如果创建的条件变量是全局的,那么可以用下面的方法进行初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
所以初始化代码如下:
pthread_cond_init(&cond,nullptr);//初始化条件变量pthread_mutex_init(&mtx,nullptr);//初始化锁
紧接着,我们创建4个线程,然后再创建一个结构体,里面包含了线程名,该线程调用的方法,条件变量和互斥锁,然后编写一个构造函数来初始化这些:
typedef void(*func_t)(const string& name,pthread_mutex_t* pmtx, pthread_cond_t* pcond);
class ThreadData
{
public:ThreadData(const string& name,func_t func,pthread_mutex_t* pmtx, pthread_cond_t* pcond):_name(name),_func(func),_pmtx(pmtx),_pcond(pcond){}
public:string _name;//线程名func_t _func;//该线程对应的回调方法pthread_mutex_t* _pmtx;//互斥锁pthread_cond_t* _pcond;//条件变量
};
然后开始编写每个线程的回调方法,这里其实不能很好地展示条件变量中锁的作用,我们需要在下一章生产者与消费者模型时,才能好好看出作用.
这里每个线程方法,我们先利用pthread_cond_wait进行阻塞等待,当资源准备就绪的时候,才会继续向后执行。然后后面输出一条语句,
该函数原型如下:
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
函数pthread_cond_wait用于使线程进入等待状态,并且会原子性地释放由mutex指定的互斥锁,进入等待状态后会阻塞等待,直到其他线程使用相同的条件变量调用pthread_cond_signal或pthread_cond_broadcast时,被唤醒并重新获得互斥锁。调用前需要确保已经加锁。返回时会重新获得互斥锁。
其中第一个参数为条件变量,第二个参数为互斥锁。具体什么作用,下节课会讲。
这是线程1的回调方法,线程2,3,4都是同样地,只不过名字不同。
void func1(const string& name,pthread_mutex_t* pmtx, pthread_cond_t* pcond)
{while(true){pthread_mutex_lock(pmtx);//if(临界资源不就绪) wait之前一般会进行检测,这里由于无法很好的模拟场景,就暂时不加ifpthread_cond_wait(pcond,pmtx);//默认该线程在执行的时候,wait代码被执行,当前线程会立即被阻塞cout << name << "running ..." << endl;pthread_mutex_unlock(pmtx);}
}
现在创建好线程后,每个线程都被阻塞在了pthread_mutex_wait接口这里,所以我们需要再主函数中唤醒这些线程,共有两种方式:
pthread_cond_signal
int pthread_cond_signal(pthread_cond_t *cond);
参数为条件变量,至于唤醒哪一个线程,这是由调度器决定的,但顺序一定是固定的,当我们运行起来程序后:

这样便保证了线程同步。
这是一个一个线程的唤醒,如果我们想唤醒所有线程,这就需要用到pthread_cond_broadcast,
该函数原型如下:
int pthread_cond_broadcast(pthread_cond_t *cond);
参数同样也为条件变量,函数pthread_cond_broadcast用于广播条件变量的信号,即唤醒所有等待此条件变量的线程。

这样便一次能唤醒所有线程继续执行了。
一切完成之后,我们需要在最后销毁释放条件变量和互斥锁,函数为pthread_cond_destroy,
函数原型为:
int pthread_cond_destroy(pthread_cond_t *cond);
参数同样为定义的条件变量,传进去之后,即可释放条件变量。
整体代码
以上便是条件变量的一个大致使用流程,具体的理解下一章生产者消费者模型会讲解,这列理解了条件变量的用法即可。
可以拷贝到自己平台下运行,编译时记得加上-lpthread,如下:
g++ -o mythread mythread.cc -lpthread
代码:
#include<iostream>
#include<string>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>using namespace std;#define PTHREAD_NUM 4typedef void(*func_t)(const string& name,pthread_mutex_t* pmtx, pthread_cond_t* pcond);
class ThreadData
{
public:ThreadData(const string& name,func_t func,pthread_mutex_t* pmtx, pthread_cond_t* pcond):_name(name),_func(func),_pmtx(pmtx),_pcond(pcond){}
public:string _name;func_t _func;pthread_mutex_t* _pmtx;pthread_cond_t* _pcond;
};void func1(const string& name,pthread_mutex_t* pmtx, pthread_cond_t* pcond)
{while(true){pthread_mutex_lock(pmtx);//if(临界资源不就绪) wait之前一般会进行检测,这里由于无法很好的模拟场景,就暂时不加ifpthread_cond_wait(pcond,pmtx);//默认该线程在执行的时候,wait代码被执行,当前线程会立即被阻塞cout << name << "running ..." << endl;pthread_mutex_unlock(pmtx);}
}
void func2(const string& name,pthread_mutex_t* pmtx, pthread_cond_t* pcond)
{while(true){pthread_mutex_lock(pmtx);pthread_cond_wait(pcond,pmtx);//默认该线程在执行的时候,wait代码被执行,当前线程会立即被阻塞cout << name << "running ..." << endl;pthread_mutex_unlock(pmtx);}
}
void func3(const string& name,pthread_mutex_t* pmtx, pthread_cond_t* pcond)
{while(true){pthread_mutex_lock(pmtx);pthread_cond_wait(pcond,pmtx);//默认该线程在执行的时候,wait代码被执行,当前线程会立即被阻塞cout << name << "running ..." << endl;pthread_mutex_unlock(pmtx);}
}
void func4(const string& name,pthread_mutex_t* pmtx, pthread_cond_t* pcond)
{while(true){pthread_mutex_lock(pmtx);pthread_cond_wait(pcond,pmtx);//默认该线程在执行的时候,wait代码被执行,当前线程会立即被阻塞cout << name << "running ..." << endl;pthread_mutex_unlock(pmtx);}
}void* Entry(void* args)
{ThreadData* td = (ThreadData*)args;//td在每一个线程自己私有的栈空间保存td->_func(td->_name,td->_pmtx,td->_pcond);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 tids[PTHREAD_NUM];//定义四个线程的回调方法func_t funcs[PTHREAD_NUM] = {func1,func2,func3,func4};for(int i = 0; i < PTHREAD_NUM; i++){string name = "thread ";name += to_string(i+1);ThreadData* td = new ThreadData(name,funcs[i],&mtx,&cond);pthread_create(tids+i,nullptr,Entry,(void*)td);}//这里为了方便演示pthread_cond_wait,在没有用signal或broadcast唤醒前,一直处于阻塞状态sleep(5);//控制线程while(true){cout << "wake up thread run code ..." << endl;//pthread_cond_signal(&cond);//唤醒一个线程pthread_cond_broadcast(&cond);//唤醒全部线程sleep(1);}for(int i = 0; i < PTHREAD_NUM; i++){pthread_join(tids[i],nullptr);}pthread_mutex_destroy(&mtx);pthread_cond_destroy(&cond);return 0;
}
相关文章:
【Linux】线程同步和死锁
目录 死锁 什么是死锁 构成死锁的四个必要条件 如何避免死锁 线程同步 同步的引入 同步的方式 条件变量 条件变量的使用 整体代码 死锁 什么是死锁 死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放 的资源而处…...
Matplotlib数据可视化(二)
目录 1.rc参数设置 1.1 lines.linestype取值 1.2 lines.marker参数的取值 1.3 绘图中文预设 1.4 示例 1.4.1 示例1 1.4.2 示例2 1.rc参数设置 利用matplotlib绘图时为了让绘制出的图形更加好看,需要对参数进行设置rc参数设置。可以通过以下代码查看matplotli…...
图像去雨-雨线清除-图像处理-(计算机作业附代码)
背景 多年来,图像去雨已经被广泛研究,使用传统方法和基于学习的方法。然而,传统方法如高斯混合模型和字典学习方法耗时,并且无法很好地处理受到严重雨滴影响的图像块。 算法 通过考虑雨滴条状特性和角度分布,这个问…...
pycharm调整最大堆发挥最大
python程序运行时,怎么提高效率,设置pycharm最大堆过程如下; 一、进入设置pycharm最大堆; 二、进入设置pycharm最大堆; 如果8g设置为6g左右,占75%左右最佳...
uni-app 经验分享,从入门到离职(二)—— tabBar 底部导航栏实战基础篇
文章目录 📋前言⏬关于专栏 🎯关于小程序 tabbar 的一些知识🎯创建一个基本的 tabBar📝最后 📋前言 这篇文章的内容主题是关于小程序的 tabBar 底部导航栏的入门使用和实战技巧。通过上一篇文章的基础,我们…...
【李沐】3.2线性回归从0开始实现
%matplotlib inline import random import torch from d2l import torch as d2l1、生成数据集: 看最后的效果,用正态分布弄了一些噪音 上面这个具体实现可以看书,又想了想还是上代码把: 按照上面生成噪声,其中最后那…...
一百五十六、Kettle——Linux上安装的Kettle9.3连接ClickHouse数据库(亲测,附流程截图)
一、目标 kettle9.3在Linux上安装好后,需要与ClickHouse数据库建立连接 二、前提准备 (一)在Linux已经安装好kettle并可以启动kettle (二)已知kettle和ClickHouse版本 1、kettle版本是9.3 2、ClickHouse版本是21…...
图数据库_Neo4j和SpringBoot整合使用_创建节点_删除节点_创建关系_使用CQL操作图谱---Neo4j图数据库工作笔记0009
首先需要引入依赖 springboot提供了一个spring data neo4j来操作 neo4j 可以看到它的架构 这个是下载下来的jar包来看看 有很多cypher对吧 可以看到就是通过封装的驱动来操作graph database 然后开始弄一下 首先添加依赖...
Uniapp连接蓝牙设备
一、效果图 二、流程图 三、实现 UI <uni-list><uni-list :border="true"><!-- 显示圆形头像 -->...
linux切换到root用户:su root和sudo su命令的区别
前言 工作过程中遇到需要切换到root用户下去执行命令 方法1:工作中常会选择这个方法 利用su root命令 临时获取root用户权限,工作目录不变 好处:不需要知道root用户的密码,直接输入普通用户的密码即可 方法2 利用sudo su命…...
kafka-- kafka集群 架构模型职责分派讲解
一、 kafka集群 架构模型职责分派讲解 生产者将消息发送到相应的Topic,而消费者通过从Topic拉取消息来消费 Kafka奇数个节点消费者consumer会将消息拉去过来生产者producer会将消息发送出去数据管理 放在zookeeper...
Effective C++条款07——为多态基类声明virtual析构函数(构造/析构/赋值运算)
有许多种做法可以记录时间,因此,设计一个TimeKeeper base class和一些derived classes 作为不同的计时方法,相当合情合理: class TimeKeeper { public:TimeKeeper();~TimeKeeper();// ... };class AtomicClock: public TimeKeepe…...
用友Java后端笔试2023-8-5
计算被直线划分区域 在笛卡尔坐标系,存在区域[A,B],被不同线划分成多块小的区域,简单起见,假设这些不同线都直线并且不存在三条直线相交于一点的情况。 img 那么,如何快速计算某个时刻,在 X 坐标轴上[ A,…...
idea2023 springboot2.7.5+mybatis+jsp 初学单表增删改查
创建项目 因为2.7.14使用量较少,特更改spring-boot为2.7.5版本 配置端口号 打开Sm01Application类,右键运行启动项目,或者按照如下箭头启动 启动后,控制台提示如下信息表示成功 此刻在浏览器中输入:http://lo…...
大语言模型之四-LlaMA-2从模型到应用
最近开源大语言模型LlaMA-2火出圈,从huggingface的Open LLM Leaderboard开源大语言模型排行榜可以看到LlaMA-2还是非常有潜力的开源商用大语言模型之一,相比InstructGPT,LlaMA-2在数据质量、培训技术、能力评估、安全评估和责任发布方面进行了…...
Android 远程真机调研
背景 现有的安卓测试机器较少,很难满足 SDK 的兼容性测试及线上问题(特殊机型)验证,基于真机成本较高且数量较多的前提下,可以考虑使用云测平台上的机器进行验证,因此需要针对各云测平台进行调研、比较。 …...
B. 攻防演练 (2021CCPC女生赛)
题意: 给出一个长度为n的字符,字符是前m个小写字母,有q个询问,每次询问一个最短子序列的长度满足不是[l,r]内任意一个子序列 思路: [l,r]中子序列可以看成是从[l,r]中的某个位置开始,跳到下一个字符的位…...
MAC环境,在IDEA执行报错java: -source 1.5 中不支持 diamond 运算符
Error:(41, 51) java: -source 1.5 中不支持 diamond 运算符 (请使用 -source 7 或更高版本以启用 diamond 运算符) 进入设置 修改java版本 pom文件中加入 <plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin&l…...
Tomcat日志中文乱码
修改安装目录下的日志配置 D:\ProgramFiles\apache-tomcat-9.0.78\conf\logging.properties java.util.logging.ConsoleHandler.encoding GBK...
最小生成树 — Prim算法
同Kruskal算法一样,Prim算法也是最小生成树的算法,但与Kruskal算法有较大的差别。 Prim算法整体是通过“解锁” “选中”的方式,点 -> 边 -> 点 -> 边。 因为是最小生成树,所以针对的也是无向图,所以可以随意…...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...
C++实现分布式网络通信框架RPC(2)——rpc发布端
有了上篇文章的项目的基本知识的了解,现在我们就开始构建项目。 目录 一、构建工程目录 二、本地服务发布成RPC服务 2.1理解RPC发布 2.2实现 三、Mprpc框架的基础类设计 3.1框架的初始化类 MprpcApplication 代码实现 3.2读取配置文件类 MprpcConfig 代码实现…...
【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道
文/法律实务观察组 在债务重组领域,专业机构的核心价值不仅在于减轻债务数字,更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明,合法债务优化需同步实现三重平衡: 法律刚性(债…...
