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

【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】线程同步和死锁

目录 死锁 什么是死锁 构成死锁的四个必要条件 如何避免死锁 线程同步 同步的引入 同步的方式 条件变量 条件变量的使用 整体代码 死锁 什么是死锁 死锁是指在一组进程中的各个进程均占有不会释放的资源&#xff0c;但因互相申请被其他进程所占用不会释放 的资源而处…...

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绘图时为了让绘制出的图形更加好看&#xff0c;需要对参数进行设置rc参数设置。可以通过以下代码查看matplotli…...

图像去雨-雨线清除-图像处理-(计算机作业附代码)

背景 多年来&#xff0c;图像去雨已经被广泛研究&#xff0c;使用传统方法和基于学习的方法。然而&#xff0c;传统方法如高斯混合模型和字典学习方法耗时&#xff0c;并且无法很好地处理受到严重雨滴影响的图像块。 算法 通过考虑雨滴条状特性和角度分布&#xff0c;这个问…...

pycharm调整最大堆发挥最大

python程序运行时&#xff0c;怎么提高效率&#xff0c;设置pycharm最大堆过程如下&#xff1b; 一、进入设置pycharm最大堆&#xff1b; 二、进入设置pycharm最大堆&#xff1b; 如果8g设置为6g左右&#xff0c;占75%左右最佳...

uni-app 经验分享,从入门到离职(二)—— tabBar 底部导航栏实战基础篇

文章目录 &#x1f4cb;前言⏬关于专栏 &#x1f3af;关于小程序 tabbar 的一些知识&#x1f3af;创建一个基本的 tabBar&#x1f4dd;最后 &#x1f4cb;前言 这篇文章的内容主题是关于小程序的 tabBar 底部导航栏的入门使用和实战技巧。通过上一篇文章的基础&#xff0c;我们…...

【李沐】3.2线性回归从0开始实现

%matplotlib inline import random import torch from d2l import torch as d2l1、生成数据集&#xff1a; 看最后的效果&#xff0c;用正态分布弄了一些噪音 上面这个具体实现可以看书&#xff0c;又想了想还是上代码把&#xff1a; 按照上面生成噪声&#xff0c;其中最后那…...

一百五十六、Kettle——Linux上安装的Kettle9.3连接ClickHouse数据库(亲测,附流程截图)

一、目标 kettle9.3在Linux上安装好后&#xff0c;需要与ClickHouse数据库建立连接 二、前提准备 &#xff08;一&#xff09;在Linux已经安装好kettle并可以启动kettle &#xff08;二&#xff09;已知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&#xff1a;工作中常会选择这个方法 利用su root命令 临时获取root用户权限&#xff0c;工作目录不变 好处&#xff1a;不需要知道root用户的密码&#xff0c;直接输入普通用户的密码即可 方法2 利用sudo su命…...

kafka-- kafka集群 架构模型职责分派讲解

一、 kafka集群 架构模型职责分派讲解 生产者将消息发送到相应的Topic&#xff0c;而消费者通过从Topic拉取消息来消费 Kafka奇数个节点消费者consumer会将消息拉去过来生产者producer会将消息发送出去数据管理 放在zookeeper...

Effective C++条款07——为多态基类声明virtual析构函数(构造/析构/赋值运算)

有许多种做法可以记录时间&#xff0c;因此&#xff0c;设计一个TimeKeeper base class和一些derived classes 作为不同的计时方法&#xff0c;相当合情合理&#xff1a; class TimeKeeper { public:TimeKeeper();~TimeKeeper();// ... };class AtomicClock: public TimeKeepe…...

用友Java后端笔试2023-8-5

计算被直线划分区域 在笛卡尔坐标系&#xff0c;存在区域[A,B],被不同线划分成多块小的区域&#xff0c;简单起见&#xff0c;假设这些不同线都直线并且不存在三条直线相交于一点的情况。 img 那么&#xff0c;如何快速计算某个时刻&#xff0c;在 X 坐标轴上[ A&#xff0c;…...

idea2023 springboot2.7.5+mybatis+jsp 初学单表增删改查

创建项目 因为2.7.14使用量较少&#xff0c;特更改spring-boot为2.7.5版本 配置端口号 打开Sm01Application类&#xff0c;右键运行启动项目&#xff0c;或者按照如下箭头启动 启动后&#xff0c;控制台提示如下信息表示成功 此刻在浏览器中输入&#xff1a;http://lo…...

大语言模型之四-LlaMA-2从模型到应用

最近开源大语言模型LlaMA-2火出圈&#xff0c;从huggingface的Open LLM Leaderboard开源大语言模型排行榜可以看到LlaMA-2还是非常有潜力的开源商用大语言模型之一&#xff0c;相比InstructGPT&#xff0c;LlaMA-2在数据质量、培训技术、能力评估、安全评估和责任发布方面进行了…...

Android 远程真机调研

背景 现有的安卓测试机器较少&#xff0c;很难满足 SDK 的兼容性测试及线上问题&#xff08;特殊机型&#xff09;验证&#xff0c;基于真机成本较高且数量较多的前提下&#xff0c;可以考虑使用云测平台上的机器进行验证&#xff0c;因此需要针对各云测平台进行调研、比较。 …...

B. 攻防演练 (2021CCPC女生赛)

题意&#xff1a; 给出一个长度为n的字符&#xff0c;字符是前m个小写字母&#xff0c;有q个询问&#xff0c;每次询问一个最短子序列的长度满足不是[l,r]内任意一个子序列 思路&#xff1a; [l,r]中子序列可以看成是从[l,r]中的某个位置开始&#xff0c;跳到下一个字符的位…...

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算法一样&#xff0c;Prim算法也是最小生成树的算法&#xff0c;但与Kruskal算法有较大的差别。 Prim算法整体是通过“解锁” “选中”的方式&#xff0c;点 -> 边 -> 点 -> 边。 因为是最小生成树&#xff0c;所以针对的也是无向图&#xff0c;所以可以随意…...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

生成xcframework

打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式&#xff0c;可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

FastAPI 教程:从入门到实践

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;支持 Python 3.6。它基于标准 Python 类型提示&#xff0c;易于学习且功能强大。以下是一个完整的 FastAPI 入门教程&#xff0c;涵盖从环境搭建到创建并运行一个简单的…...

测试markdown--肇兴

day1&#xff1a; 1、去程&#xff1a;7:04 --11:32高铁 高铁右转上售票大厅2楼&#xff0c;穿过候车厅下一楼&#xff0c;上大巴车 &#xffe5;10/人 **2、到达&#xff1a;**12点多到达寨子&#xff0c;买门票&#xff0c;美团/抖音&#xff1a;&#xffe5;78人 3、中饭&a…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...

【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统

目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索&#xff08;基于物理空间 广播范围&#xff09;2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

【生成模型】视频生成论文调研

工作清单 上游应用方向&#xff1a;控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

C语言中提供的第三方库之哈希表实现

一. 简介 前面一篇文章简单学习了C语言中第三方库&#xff08;uthash库&#xff09;提供对哈希表的操作&#xff0c;文章如下&#xff1a; C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...

TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?

在工业自动化持续演进的今天&#xff0c;通信网络的角色正变得愈发关键。 2025年6月6日&#xff0c;为期三天的华南国际工业博览会在深圳国际会展中心&#xff08;宝安&#xff09;圆满落幕。作为国内工业通信领域的技术型企业&#xff0c;光路科技&#xff08;Fiberroad&…...

AI语音助手的Python实现

引言 语音助手(如小爱同学、Siri)通过语音识别、自然语言处理(NLP)和语音合成技术,为用户提供直观、高效的交互体验。随着人工智能的普及,Python开发者可以利用开源库和AI模型,快速构建自定义语音助手。本文由浅入深,详细介绍如何使用Python开发AI语音助手,涵盖基础功…...