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

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);

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

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

相关文章:

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

目录 一、什么是死锁 死锁的四个必要条件 如何避免死锁 避免死锁算法 二、Linux线程同步 三 、条件变量 1、条件变量基本原理 2、条件变量的使用 3、条件变量使用示例 为什么 pthread_cond_wait 需要互斥量? 一、什么是死锁 死锁是计算机科学中的一个概念&#xff0c;…...

SpringAMQP 发布订阅-TopicExchange

根据这个模型编写代码: RabbitListener(bindings QueueBinding(value Queue(name "topic.queue1"),exchange Exchange(name "itcast.topic",type ExchangeTypes.TOPIC),key {"china.#"}))public void listenTopicQueue1(String msg){Syst…...

uniapp h5 配置代理服务器

"devServer": {"disableHostCheck": true,"proxy": {"/api": {// 需要被代理的后台地址"target": "http://自己的地址","changeOrigin": true,"secure": false,"pathRewrite": {&q…...

使用Apache Spark从MySQL到Kafka再到HDFS的数据转移

使用Apache Spark从MySQL到Kafka再到HDFS的数据转移 在本文中&#xff0c;将介绍如何构建一个实时数据pipeline&#xff0c;从MySQL数据库读取数据&#xff0c;通过Kafka传输数据&#xff0c;最终将数据存储到HDFS中。我们将使用Apache Spark的结构化流处理和流处理功能&#…...

一篇文章拿下Redis 通用命令

文章目录 Redis数据结构介绍Redis 通用命令命令演示KEYSDELEXISTSEXPIRE RedisTemplate 中的通用命令 本篇文章介绍 Redis 的通用命令, 通用命令在 Redis 的所有数据类型下都使用, 学好通用命令可以让我们更好的使用 Redis. Redis数据结构介绍 Redis 是一个key-value的数据库&…...

锂电池充电充放电曲线分析

前言 锂电池的充电曲线通常包括三个阶段:恒流充电阶段、恒压充电阶段和滞后充电阶段。在恒流充电阶段,电流保持恒定,电压逐渐增加;在恒压充电阶段,电压保持恒定,电流逐渐减小;在滞后充电阶段,电流进一步减小,电池开始充满。通过监测这些阶段的电流和电压变化,可以评…...

vue3 第二十九节 (vue3 事件循环之nextTick)

引言 vue 项目中为什么要使用 nextTick 这个函数&#xff0c;是做什么用的&#xff0c;解决了哪些问题 1、nextTick 作用 用于处理DOM更新完成之后&#xff0c;执行回调函数的方法&#xff1b; 2、实现方案 vue2 中 nextTick() 是基于浏览器的 异步队列和微任务队列而执行…...

使用Flask-SocketIO构建实时Web应用

文章目录 准备工作编写代码编写HTML模板运行应用 随着互联网的发展&#xff0c;实时性成为了许多Web应用的重要需求之一。传统的HTTP协议虽然可以实现实时通信&#xff0c;但是其长轮询等机制效率低下&#xff0c;无法满足高并发、低延迟的需求。为了解决这一问题&#xff0c;诞…...

可重构柔性装配产线:为工业制造领域注入了新的活力

随着科技的飞速发展&#xff0c;智能制造正逐渐成为引领工业革新的重要力量。在这一浪潮中&#xff0c;可重构柔性装配产线以其独特的技术优势和创新理念&#xff0c;为工业制造领域注入了新的活力&#xff0c;开启了创新驱动的智能制造新篇章。 可重构柔性装配产线是基于富唯智…...

懒人网址导航源码v3.9

测试环境 宝塔Nginx -Tengine2.2.3的PHP5.6 MySQL5.6.44 为防止调试错误&#xff0c;建议使用测试环境运行的php与mysql版本 首先用phpMyAdmin导入数据库文件db/db.sql 如果导入不行&#xff0c;请直接复制数据库内容运行sql语句也可以 再修改config.php来进行数据库配置…...

springboot 开启缓存 @EnableCaching(使用redis)

添加依赖 pom.xml <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency>application.yml 配置redis连参数 spring:# redis 配置redis:# 地址host: 127.0.0.…...

Adobe After Effects AE v24.3.0 解锁版 (视频合成及视频特效制作)

Adobe系列软件安装目录 一、Adobe Photoshop PS 25.6.0 解锁版 (最流行的图像设计软件) 二、Adobe Media Encoder ME v24.3.0 解锁版 (视频和音频编码渲染工具) 三、Adobe Premiere Pro v24.3.0 解锁版 (领先的视频编辑软件) 四、Adobe After Effects AE v24.3.0 解锁版 (视…...

Qt---文件系统

一、基本文件操作 1. QFile对文件进行读和写 QFile file( path 文件路径) 读&#xff1a; file.open(打开方式) QlODevice::readOnly 全部读取->file.readAll()&#xff0c;按行读->file.readLine()&#xff0c;atend()->判断是否读到文件尾 …...

ruoyi-vue-pro 使用记录(2)

ruoyi-vue-pro 使用记录&#xff08;2&#xff09; 数据库商城商品模块数据表营销数据库交易数据库统计数据库 数据库 商城 参考官方文档 ruoyi-vue-pro yudao 项目商城 mall 模块启用及相关SQL脚本 商品模块&#xff08;中心&#xff09;以 product_ 作为前缀的表交易模块…...

centos7中如何全局搜索一下nginx的配置文件?

在CentOS 7中搜索Nginx的配置文件&#xff0c;你可以使用一些常用的命令行工具&#xff0c;比如find、grep等。这些工具可以帮助你在文件系统中查找文件&#xff0c;也可以用来查找Docker容器内部的文件&#xff0c;只要你知道如何访问容器的文件系统。 1. 搜索系统中的Nginx配…...

2024年5月10日有感复盘

2024年5月10日有感复盘 时间 今天是一个很美好的一天&#xff0c;原因是很平凡&#xff0c;读书很平凡&#xff0c;玩游戏很平凡&#xff0c;然后生活很平凡&#xff0c;未来可期&#xff0c;听歌很舒服&#xff0c;很喜欢一个人呆在图书馆的感觉&#xff0c;很喜欢发呆&…...

C++通过json文件配置参数

一、安装nlohmann json nlohmann json&#xff1a;安装_nlohmann安装-CSDN博客 依次执行下面指令&#xff1a; git clone https://gitee.com/cuihongxi/mov_from_github.gitcd json-developmkdir buildcd buildcmake ..makesudo make install 二、安装完成后使用 #include…...

idea连接远程仓库

git ->克隆。 url为远程仓库的地址&#xff0c;输入好后&#xff0c;选择项目存放目录&#xff0c;再点击克隆 点击新窗口打开。 切换到对应分支...

初始Django

初始Django 一、Django的历史 ​ Django 是从真实世界的应用中成长起来的&#xff0c;它是由堪萨斯&#xff08;Kansas&#xff09;州 Lawrence 城中的一个网络开发小组编写的。它诞生于 2003 年秋天&#xff0c;那时 Lawrence Journal-World 报纸的程序员 Adrian Holovaty 和…...

leetcode56--合并区间

题目描述 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 示例 1&#xff1a; 输入&#xff1a;interv…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

RocketMQ延迟消息机制

两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数&#xff0c;对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后&#xf…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

基于服务器使用 apt 安装、配置 Nginx

&#x1f9fe; 一、查看可安装的 Nginx 版本 首先&#xff0c;你可以运行以下命令查看可用版本&#xff1a; apt-cache madison nginx-core输出示例&#xff1a; nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

相机从app启动流程

一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

华为OD机考-机房布局

import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...

破解路内监管盲区:免布线低位视频桩重塑停车管理新标准

城市路内停车管理常因行道树遮挡、高位设备盲区等问题&#xff0c;导致车牌识别率低、逃费率高&#xff0c;传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法&#xff0c;正成为破局关键。该设备安装于车位侧方0.5-0.7米高度&#xff0c;直接规避树枝遮…...

沙箱虚拟化技术虚拟机容器之间的关系详解

问题 沙箱、虚拟化、容器三者分开一一介绍的话我知道他们各自都是什么东西&#xff0c;但是如果把三者放在一起&#xff0c;它们之间到底什么关系&#xff1f;又有什么联系呢&#xff1f;我不是很明白&#xff01;&#xff01;&#xff01; 就比如说&#xff1a; 沙箱&#…...

倒装芯片凸点成型工艺

UBM&#xff08;Under Bump Metallization&#xff09;与Bump&#xff08;焊球&#xff09;形成工艺流程。我们可以将整张流程图分为三大阶段来理解&#xff1a; &#x1f527; 一、UBM&#xff08;Under Bump Metallization&#xff09;工艺流程&#xff08;黄色区域&#xff…...