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

news2024/11/24 10:55:55

目录

一、什么是死锁

死锁的四个必要条件

如何避免死锁

避免死锁算法

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++指针和动态内存分配细节,反汇编,面试题05

文章目录 20. 指针 vs 引用21. new vs malloc 20. 指针 vs 引用 指针是实体&#xff0c;占用内存空间&#xff0c;逻辑上独立&#xff1b;引用是别名&#xff0c;与变量共享内存空间&#xff0c;逻辑上不独立。指针定义时可以不初始化&#xff1b;引用定义时必须初始化。指针的…

物理机转换成虚拟机之linux

文章目录 注意事项环境准备网络拓扑VMware Converter开始转换 注意事项 目标系统-vCenter-ESXI-VMwareConverter网络必须是互通的&#xff0c;否则&#xff0c;会卡在1% 环境准备 vCenteresxiVMware Converter 网络拓扑 VMware Converter开始转换

20 分页:较小的表

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

如何进行资产梳理(信息收集)

前言 渗透测试流程 线路一:渗透测试人员 1.域名收集--(备案收集) 1.1在线收集子域名 1.1.1 站长之家 1.1.2 IP138网站 1.1.3 查子域 1.1.4 RapidDNS 1.1.5 聚名 1.1.6 Crt.sh 1.1.7 googleHack 1.2工具和资产测绘收集子域名 1.2.1 oneforall(最好用,最全面) 1.2.…

火山引擎VeDI:A/B测试平台指标能力升级,助力企业提升精细化运营效率

在数字化浪潮的推动下&#xff0c;数据分析与精细化运营已成为企业提升竞争力的关键。近日&#xff0c;火山引擎A/B测试DataTester完成了指标能力的全面升级&#xff0c;为企业在流量竞争激烈的市场中提供了更强大、更可信的数据支持。 此次升级亮点在于引入了“按某个属性去重…

AI大模型探索之路-训练篇22: ChatGLM3微调实战-从原理到应用的LoRA技术全解

系列篇章&#x1f4a5; AI大模型探索之路-训练篇1&#xff1a;大语言模型微调基础认知 AI大模型探索之路-训练篇2&#xff1a;大语言模型预训练基础认知 AI大模型探索之路-训练篇3&#xff1a;大语言模型全景解读 AI大模型探索之路-训练篇4&#xff1a;大语言模型训练数据集概…

令牌桶算法:如何优雅地处理突发流量?

令牌桶算法的介绍 在网络流量控制和请求限流中&#xff0c;令牌桶算法是一种常用的策略。那么&#xff0c;令牌桶算法到底是什么呢&#xff1f;它的工作原理又是怎样的呢&#xff1f;让我们一起来探索一下。 令牌桶算法&#xff0c;顾名思义&#xff0c;就是有一个存放令牌的…

云原生技术解析

云原生的概念 云原生是一种软件架构和部署方法&#xff0c;旨在利用云计算的优势&#xff0c;以更灵活、可扩展和可靠的方式构建和部署应用程序。它主要关注在容器、微服务、自动化和持续交付等方面。 云原生技术是指以云计算作为基础&#xff0c;以平台和工具为依托&#xff0…

大规模 RGB LED灯控系统 Lumos:创新与智能化的融合

灯控系统&#xff1a;创新与智能化的融合 在现代照明技术不断进步的背景下&#xff0c;灯控系统的应用已经从简单的开关控制&#xff0c;发展到能够进行复杂程控操作的智能化管理。我们推出的新一代灯控解决方案&#xff0c;凭借其高度的可配置性和跨平台兼容性&#xff0c;已…

Hadopp入门之基础概念

Hadoop概述 Hadoop是什么 Hadoop是一个由Apache基金会所开发的分布式系统基础架构主要解决海量数据的存储和海量数据的分析计算问题广义上来说&#xff0c;Hadoop通常是指一个更广泛的概念——Hadoop生态圈 Hadoop优势 高可靠性&#xff1a;Hadoop底层维护多个数据副本&…

Linux基础之进程-fork()函数的详解

目录 一、前言 二、fork()函数 2.1 fork()函数的基本概念 2.2 问题一的解答 2.3 问题二的解答 2.4 问题三的解答 2.5 问题四的解答 2.6 问题五的解答 一、前言 在上节内容中我们已经学会了使用我们的getpid()和我们的getppid()去查看我们进程的pid&#xff0c;并且学习到…

通过Mendix Portal管理应用整个生命周期

一、前言 大家常常会听到Mendix是一个统一的平台&#xff0c;怎么理解这个统一平台呢&#xff1f;它指的是帮助企业搭建一个统一的开发平台&#xff0c;管理应用的整个生命周期&#xff0c;之前大家更多地关注在应用开发层面&#xff0c;而开发只是整个生命周期的一环。 从上图…

华为手机恢复出厂设置后怎么还原数据?该如何预防数据丢失?

华为手机恢复出厂设置是将手机恢复到出厂时的初始状态&#xff0c;同时会删除所有用户数据和个人设置。如果不做任何预防措施&#xff0c;在恢复出厂设置后&#xff0c;您将丢失手机上的所有数据。那华为手机恢复出厂设置后怎么还原数据呢&#xff1f;以下是关于如何在华为手机…

柔性数组+结构体类型转换

柔性数组&#xff1a;在结构体中声明的时候仅作为占位符&#xff0c;好处是地址是连续的 强制类型转换&#xff1a;可用于通信双方进行信息交流 #include <iostream> #include <string.h>struct DataWater {int count;float size;char buf[0]; }; // dbuf相当于是…

GLU(Gated Linear Unit) 门控线性单元

文章目录 一、RNN二、GLU2.1 整体结构2.2 输入层(Input SentenceLookup Table)2.3 中间层(ConvolutionGate)2.4 输出层(Softmax)2.5 实验结果2.6 实现代码 三、RNN与GLU的对比参考资料 GLU可以理解为能够并行处理时序数据的CNN网络架构&#xff0c;即利用CNN及门控机制实现了RN…

UKP3d,修改管道长度或标高的方法

南京用户问&#xff0c;请问这个起末点标高可以修改么&#xff1f;如图&#xff1a; 上述起末点的标高是不可以修改&#xff0c;用户没有详细阐述自已想要的结果。可能是以下几种场景&#xff0c;希望能帮助到用户&#xff1a; 1.修改管道长度&#xff1a; 1.1.(管道在模型的…

各种类型的背景音频音效,不同风格的背景音乐素材

一、素材描述 本套背景音乐素材&#xff0c;大小1.76G&#xff0c;58个压缩文件。 二、素材目录 8支简短精致的企业标志片头音乐.zip Avril 伴奏收藏 Wish you were here.zip 《带我回家》带给人温暖感动的男声歌曲 .zip 《光荣时刻》震撼激烈的英雄史诗电影音乐素材.zip…

风扇开启执行逻辑

执行流程 public static void businessExecutionWork(){//以下为业务逻辑部分System.out.println("1、根据电池包控制风扇服务执行 开始!");//1、获取电池包电压、电流、环境温度//获取电池包电压、电流、环境温度ObtainBatteryDataService obtainBatteryDataServic…

分享一个适用于 Vue3.x 非常好用的组件库【Naive UI】

一、Naive UI 介绍 Naive UI 是一种简单易用、不太复杂的用户界面&#xff08;UI&#xff09;框架&#xff0c;主要用于Web应用程序的开发。它提供了超过80个组件&#xff0c;覆盖了表格、表单、弹窗、图表等多个方面&#xff0c;这些组件不仅功能强大&#xff0c;而且高度可定…

tomcat 设置JVM 参数

tomcat 启动的服务 设置jvm 设置的文件目录&#xff1a; /tomcat/bin/catalina.sh 添加设置参数&#xff1a; JAVA_OPTS“$JAVA_OPTS -server -Xms1024m -Xmx4096m -XX:MetaspaceSize1024m -XX:MaxMetaspaceSize2048m -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/se…