Linux线程(三)死锁与线程同步

目录

一、什么是死锁

死锁的四个必要条件

如何避免死锁

避免死锁算法

 二、Linux线程同步

三 、条件变量

1、条件变量基本原理

2、条件变量的使用

3、条件变量使用示例

 为什么 pthread_cond_wait 需要互斥量?


一、什么是死锁

        死锁是计算机科学中的一个概念,特别是在操作系统和多线程编程领域中经常遇到。它指的是两个或两个以上的进程或线程在执行过程中,由于互相等待对方持有的资源而无法继续执行的状态。具体来说,每个进程都已经占有了某些资源,但还需要额外的、目前被其他进程所占有的资源才能继续执行。这样,所有涉及的进程都进入了等待状态,形成了一个相互依赖的循环,如果没有外部干预,它们将永远等待下去,无法自行解除阻塞状态。

死锁的四个必要条件

互斥条件:一个资源每次只能被一个执行流使用
请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

如何避免死锁

破坏死锁的四个必要条件(破坏其中之一即可)
加锁顺序一致
避免锁未释放的场景
资源一次性分配

避免死锁算法

死锁检测算法 是一种动态检测系统中是否已经发生死锁的方法。它不需要事先采取措施去预防死锁,而是允许系统运行,并定期检查是否有死锁的存在。基本思想是构造一个资源分配图(或称作前驱图),在这个图中,节点代表进程和资源,边表示分配关系和请求关系。如果图中存在环路,则表示系统处于死锁状态。具体步骤包括:

  1. 构建资源分配图:图中的节点分为两类,一类代表进程,另一类代表资源类型。从每个进程节点出发的边指向它已分配的资源节点,从每个资源节点出发的边指向请求该资源的进程节点。

  2. 检测环路:使用拓扑排序或深度优先搜索等算法检测图中是否存在环。如果存在环,则说明有进程等待的资源被其他在环中的进程所占有,形成死锁。

  3. 处理死锁:一旦检测到死锁,系统可以选择采取不同的策略来解决,比如终止某些进程、回滚进程状态或强制释放资源等。

银行家算法 是一种避免死锁的策略,而不是检测死锁。它通过预判分配资源的行为是否安全来避免系统进入不安全状态,从而防止死锁发生。算法核心包括以下几个步骤:

  1. 初始化:记录系统中所有可用资源的数量以及每个进程对各类资源的最大需求、已分配资源和当前还需要的资源。

  2. 安全性检查:算法在每次分配资源之前,会先检查这次分配是否会导致系统进入不安全状态。这通过试探性地分配资源,然后检查是否存在一个安全序列,即所有进程能够按照某种顺序完成执行,而不会发生某个进程因为缺少资源而无法继续的情况。

  3. 资源分配:只有当试探性分配后系统仍处于安全状态时,才会真正分配资源给请求的进程。

  4. 资源回收:当进程完成任务后,必须归还所有分配给它的资源,以便其他进程可以使用。

银行家算法的核心在于其预防机制,确保了即使在资源有限的情况下,系统也能保证进程按照某种顺序安全地执行完毕,避免了死锁的发生。

 二、Linux线程同步

        在Linux环境下,条件变量是线程同步的一种机制,用于实现线程间的协作,使得一个线程能够等待某个条件变为真,而另一个线程负责改变这个条件并通知等待的线程。条件变量通常与互斥锁一起使用,以确保在检查条件和修改条件时的原子性和一致性。

同步概念
同步(Synchronization)是指在多线程或多进程环境中,协调不同执行单元的操作顺序,确保它们按照预定的方式执行,以避免数据不一致或逻辑错误的问题。同步机制确保了对共享资源的访问是有序的,避免了竞态条件的出现。
竞态条件
竞态条件(Race Condition)是指在多线程程序中,多个线程对同一块数据进行非同步的访问和修改,其最终结果取决于线程的调度顺序。由于线程执行的交错,可能会导致数据不一致、计算错误或者程序行为不符合预期。

三 、条件变量

1、条件变量基本原理

等待条件:当一个线程发现某个条件不满足时,它可以调用pthread_cond_wait()函数,这会自动释放它之前锁定的互斥锁,并使线程进入等待状态,直到其他线程通过信号机制唤醒它。此时,线程会重新尝试获取互斥锁,并检查条件是否满足,如果不满足则可能再次进入等待状态。

发送信号:当另一个线程改变了条件变量相关的状态,并希望唤醒等待的线程时,它会调用pthread_cond_signal()pthread_cond_broadcast()函数。pthread_cond_signal()会唤醒一个等待该条件变量的线程(如果有多个线程在等待,则选择其中一个),而pthread_cond_broadcast()会唤醒所有等待该条件的线程。

2、条件变量的使用

初始化条件变量:可以通过静态初始化或者动态初始化来创建条件变量。

静态初始化使用PTHREAD_COND_INITIALIZER宏;

动态初始化则使用pthread_cond_init()函数。

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);
参数:
cond:要初始化的条件变量
attr:NULL

锁定互斥锁:在检查或修改条件之前,线程需要先获取互斥锁,以确保操作的原子性和互斥性。

检查条件:线程检查条件是否满足,如果不满足则调用pthread_cond_wait()进入等待状态。

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量,后面详细解释

改变条件:在另一个线程中,当条件改变后,应先锁定相同的互斥锁,改变条件,然后调用pthread_cond_signal()pthread_cond_broadcast()来唤醒等待线程。

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

解锁互斥锁:无论是在调用pthread_cond_wait()前后,还是在改变条件之后,都需要正确地解锁互斥锁。

清理:不再使用条件变量时,动态初始化的条件变量需要通过pthread_cond_destroy()函数进行清理。

int pthread_cond_destroy(pthread_cond_t *cond)

3、条件变量使用示例

使用条件变量(pthread_cond_t)和互斥锁(pthread_mutex_t)的经典示例,实现了线程间的简单同步。使得线程2与线程1交替打印。

代码示例:

#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("我是线程1\n");
    }
}
void *r2(void *arg)
{
    while (1)
    {
        printf("我是线程2\n");
        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);
}

r1函数作为线程1的入口点,它在一个无限循环中调用pthread_cond_wait(&cond, &mutex)。这意味着线程1会释放互斥锁mutex并阻塞,直到其他线程通过pthread_cond_signalpthread_cond_broadcast唤醒它。一旦被唤醒,它会重新获取互斥锁并打印消息“我是线程1”。

r2函数作为线程2的入口点,在其循环中打印“我是线程2”,随后调用pthread_cond_signal(&cond)来唤醒一个等待在cond上的线程(在这种情况下,通常是线程1)。之后,sleep(1)让线程2暂停1秒,模拟工作与同步的间隔。

运行结果:

 为什么 pthread_cond_wait 需要互斥量?

条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。

如果先上锁,发现条件不满足,解锁,然后等待在条件变量可以吗?

// 错误的设计
pthread_mutex_lock(&mutex);
while (condition_is_false) {
pthread_mutex_unlock(&mutex);
//解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过
pthread_cond_wait(&cond);
pthread_mutex_lock(&mutex);
}
pthread_mutex_unlock(&mutex);

如果在pthread_mutex_unlock(&mutex);pthread_cond_wait(&cond);之间,其他线程改变了条件并调用了pthread_cond_signalpthread_cond_broadcast,那么这个信号可能会被错过。因为pthread_cond_wait实际上是在调用时才检查是否应该唤醒线程,而这时线程可能已经错过了信号。

竞态条件:在解锁互斥锁后检查条件,然后等待,这期间其他线程可能又修改了条件状态,导致线程可能在不应该等待的情况下进入等待状态,或者即使条件已经满足仍然进入等待。

由于解锁和等待不是原子操作。调用解锁之后, pthread_cond_wait 之前,如果已经有其他线程获取到互斥量,摒弃条件满足,发送了信号,那么 pthread_cond_wait 将错过这个信号,可能会导致线程永远阻塞在这个 pthread_cond_wait 。所以解锁和等待必须是一个原子操作。

正确的做法是将条件检查放在pthread_cond_wait内部,确保在检查条件和等待之间不会错过任何信号。 

等待条件代码:

pthread_mutex_lock(&mutex);
while (condition_is_false) { // 条件检查放在循环内
    pthread_cond_wait(&cond, &mutex); // 等待时保持互斥锁锁定
}
// 当条件满足时,会从pthread_cond_wait返回
pthread_mutex_unlock(&mutex);
给条件发送信号代码:
pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);

这样的设计确保了当线程准备等待时,如果条件已经满足(可能是由于其他线程的操作),它不会错过这一事实,并且可以直接继续执行,避免了信号丢失和不必要的等待。

条件变量和互斥锁的正确配合使用对于避免死锁、竞态条件和信号丢失至关重要。始终遵循“在持有锁的情况下检查条件,然后等待”的原则,确保线程安全和高效的同步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/620144.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Qt---绘图和绘图设备

一、QPainter绘图 绘图事件 void paintEvent() 声明一个画家对象&#xff0c;OPainter painter(this) this指定绘图设备 画线、画圆、画矩形、画文字 设置画笔QPen 设置画笔宽度、风格 设置画刷QBrush 设置画刷风格 代码示例&#xff1a; #includ…

买一手股指期货跌多少会爆仓?

当指数下跌导致持有的股指期货合约价值减少&#xff0c;而你的保证金账户中的资金不足以维持原有的保证金要求时&#xff0c;期货公司会要求你追加保证金&#xff0c;即所谓的“追加保证金通知”。如果投资者无法及时补足保证金&#xff0c;期货公司则有权强制平仓&#xff0c;…

【Linux】磁盘文件

思维导图 学习目标 了解磁盘的物理结构和存储结构&#xff0c;并将其存储结构进行抽象&#xff01;&#xff01; 一、了解一下磁盘及其物理结构 1.1 计算机只认识二进制 什么是二进制&#xff1f;&#xff1f;0&#xff0c;1是被规定出来的&#xff0c;在计算机里面我们用高低…

SAP 控制已转采购订单的PR不允许删除简介

SAP系统中采购申请当被转成采购订单后&#xff0c;在采购申请中会关联到对应已生生成的采购订单&#xff0c;如下图中可以看到采购申请对应的采购订单 当日常操作中用户在创建完采购申请后&#xff0c;当PR转成PO后仍然可以将采购申请的行项目进行删除&#xff0c;显然这个操作…

『拼多多、淘宝、抖音、小红书等卖家』4个有效动作,走出成单低谷期

对于拼多多、淘宝、抖音、小红书等平台的卖家来说&#xff0c;走出成单低谷期需要一系列有效的动作。以下是店雷达四个建议&#xff1a; 一、选品优化 1、深入市场研究&#xff1a;了解当前市场趋势、消费者需求和潜在的市场空白。使用各种工具&#xff0c;如店雷达选品功能&…

Python深度学习基于Tensorflow(2)Tensorflow基础

文章目录 基本操作数据转换和数据生成操作形状数据提取和保存变量Numpy和Tensorflow的比较 计算图静态图动态图自动图 自动微分使用Tensorflow 实现回归 首先是Tensorflow的安装&#xff0c;由于可能会出现版本冲突&#xff0c;最好在conda环境安装&#xff0c;同时&#xff0c…

华为OD机试 - 密码输入检测(Java 2024 C卷 100分)

华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试…

leetcode经典例题之环形队列

P. S.&#xff1a;以下代码均在VS2019环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件stdio.h的声明&#xff0c;使用时请自行添加。 目录 1、题目展示2、问题分析3、完整代码展示4、结语 1、题目展示 在拿到题目时&#xff0c;通…

前端动态旋转地球背景

效果图 贴下源码 <template><div class"map-bg"><div class"canvas" id"canvs"></div><canvas class"canvasxk" id"canv"></canvas></div> </template><script setup …

如何快速提取出一个文件里面全部指定类型的文件的全部路径

首先&#xff0c;需要用到的这个工具&#xff1a; 度娘网盘 提取码&#xff1a;qwu2 蓝奏云 提取码&#xff1a;2r1z 打开工具&#xff0c;切换到第五个模块&#xff0c;文件批量复制模块&#xff08;快捷键&#xff1a;Ctrl5&#xff09; 点击右边的“搜索添加”按钮&#…

20 分页:较小的表

目录 简单的解决方案&#xff1a;更大的页 混合方法&#xff1a;分页和分段 多级页表 详细的多级示例 超过两级 ​编辑地址转换过程&#xff1a;记住TLB 反向页表 将页表交换到磁盘 之前提到的一个问题&#xff1a;就是页表太大&#xff0c;假设一个 32 位地址空间&…

高中数学:平面向量-基本概念

一、定义 有方向&#xff0c;且有大小的量&#xff0c;就叫向量 与之对应的是&#xff0c;数量&#xff0c;只有大小&#xff0c;没有方向 例如 A B → \mathop{AB}\limits ^{\rightarrow} AB→ a → \mathop{a}\limits ^{\rightarrow} a→ 二、相关性质 相等 大小相同…

(超详细讲解)实现将idea的java程序打包成exe (新版,可以在没有java的电脑下运行,即可以发给好朋友一起玩)

目录 实现打包到exe大概步骤 工具准备 1.将java程序文件打包成jar文件 2.准备好jre文件 3.使用exe4j软件打包好 4.最终打包 实现打包到exe大概步骤 1.打包需要满足的条件&#xff1a;将java文件转成jar文件的工具exe4j、 以及需要满足jdk1.8以上&#xff08;因安装exe4…

【必看】Spring系列面试题

Spring Core Container, AOP, Data Access, Web... 基础 1. 简单介绍Spring 一款开源的轻量级 Java 开发框架&#xff0c;旨在提高开发人员的开发效率以及系统的可维护性。Spring 支持 IoC&#xff08;Inversion of Control:控制反转&#xff09; 和 AOP(Aspect-Oriented Pro…

网络隔离状态下,如何可以安全高效地进行研发文件外发?

研发部门的数据传输通常需要保证数据的安全性、完整性和保密性&#xff0c;尤其是当涉及到公司的核心技术、产品设计、源代码等重要信息时。研发文件外发&#xff0c;即研发资料的外部传输&#xff0c;通常涉及到公司的核心技术和商业机密&#xff0c;因此需要采取严格的安全措…

动态NAT

在上一章静态NAT中我们提过了&#xff0c;静态NAT只能一对一映射&#xff0c;无法有效缓解IPV4地址池紧张的问题&#xff0c;那么我们今天来学习一个新的技术——动态NAT&#xff0c;来解决这个问题。 第一章 1.1 动态NAT工作流程 动态NAT基于地址池来实现私有地址和公有地址的…

学习软考----数据库系统工程师29

数据操作 SELECT基本结构 简单查询 连接查询 子查询 聚集函数 分组查询 字符串操作 集合操作 外连接 INSERT INTO语句 DELETE语句 UPDATE语句

融入新科技的SLM27211系列 120V, 3A/4.5A高低边高频门极驱动器兼容UCC27284,MAX15013A

SLM27211是高低边高频门极驱动器&#xff0c;集成了120V的自举二极管&#xff0c;支持高频大电流的输出&#xff0c;可在8V~17V的宽电压范围内驱动MOSFET&#xff0c;独立的高、低边驱动以方便控制&#xff0c;可用于半桥、全桥、双管正激和有源钳位正激等拓。有极好的开通、关…

下载文件名称乱码或变成了随机码

如图 后端是有正常返回附件名称的,浏览器开发工具中也正常显示了这个数据,但是下载下来的文件名称确实一堆随机码. 其实这个问题的原因是因为跨域 查看console: Refused to get unsafe header "content-disposition" 现象,后端传递到前端的fileName不能被识别,下载…

吴恩达2022机器学习专项课程C2(高级学习算法)W1(神经网络):Lab02 TensorFlow构建神经网络

这里写目录标题 实验目的导入训练集并绘制散点图特征缩放处理数据集扩展数据集TensorFlow构建神经网络模型1.设置模型的层2.获取模型信息2.优化模型3.设置模型参数3.开始预测4.转换预测结果 检测神经元的功能1.目的2.准备工作3.第一层的预测与真实数据的对比2.第二层3.神经网络…