Linux线程同步(2)死锁与互斥锁

死锁(Deadlock)是指两个或两个以上的进程(或线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(或线程)称为死锁进程(或线程)。

死锁的产生需要满足四个必要条件,这四个条件被称为死锁的四个必要条件(Coffman条件),它们是:

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
  4. 循环等待条件:系统中若干进程形成一种头尾相接的循环等待资源关系。

如果上述四个条件同时成立,系统就可能发生死锁。

操作系统层面的死锁最早在1965年由Dijkstra在研究银行家算法时提出,它是计算机操作系统乃至整个并发程序设计领域最难处理的问题之一。死锁的恢复和预防是操作系统的重要设计目标之一,常见的死锁预防和恢复策略包括鸵鸟算法(系统假装没有死锁发生)和死锁检测和恢复(系统并不试图阻止死锁的产生,而是允许死锁发生,当检测到死锁发生后,采取措施进行恢复)。

1.线程死锁

本节基于线程去讲解死锁。

有时,一个线程需要同时访问两个或更多不同的共享资源,而每个资源又都由不同的互斥量管理。当超过一个线程加锁同一组互斥量时,就有可能发生死锁。

两个或两个以上的进程在执行过程中,因争夺共享资源而造成的一种互相等待的现象若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁

死锁的几种场景:(1)忘记释放锁;(2)重复加锁;(3)多线程多锁,抢占锁资源

(2)连续两次(或多次)调用 pthread_mutex_lock 来锁定同一个互斥量(mutex)而没有相应的 pthread_mutex_unlock 调用会导致死锁(deadlock)。这是因为互斥量的设计初衷是确保同一时间只有一个线程可以访问被保护的资源或代码段。

当你第一次调用 pthread_mutex_lock(&mutex); 时,如果互斥量 mutex 已经被其他线程锁定,当前线程将会阻塞,直到互斥量被解锁。如果当前线程已经拥有了这个互斥量(即它之前已经成功锁定了这个互斥量但还没有解锁),那么再次调用 pthread_mutex_lock(&mutex); 会导致线程阻塞,因为它在等待自己释放互斥量,这是一个逻辑错误。

测试代码:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>// 创建2个互斥量
pthread_mutex_t mutex1, mutex2;void * workA(void * arg) {pthread_mutex_lock(&mutex1);sleep(1);pthread_mutex_lock(&mutex2);printf("workA....\n");pthread_mutex_unlock(&mutex2);pthread_mutex_unlock(&mutex1);return NULL;
}void * workB(void * arg) {pthread_mutex_lock(&mutex2);sleep(1);pthread_mutex_lock(&mutex1);printf("workB....\n");pthread_mutex_unlock(&mutex1);pthread_mutex_unlock(&mutex2);return NULL;
}int main() {// 初始化互斥量pthread_mutex_init(&mutex1, NULL);pthread_mutex_init(&mutex2, NULL);// 创建2个子线程pthread_t tid1, tid2;pthread_create(&tid1, NULL, workA, NULL);pthread_create(&tid2, NULL, workB, NULL);// 回收子线程资源pthread_join(tid1, NULL);pthread_join(tid2, NULL);// 释放互斥量资源pthread_mutex_destroy(&mutex1);pthread_mutex_destroy(&mutex2);return 0;
}

上述代码,最终执行不会输出任何东西。

首先,workA对1加锁,然后睡眠1秒。这个时候可能轮到B执行了,B先对2进行加锁,然后睡眠1秒。此时A醒了,尝试对2进行加锁,而2已经被B加锁了,因此A阻塞。轮到B的时候,对1进行加锁,然后1已经被A加上了,所以B阻塞等待。所有,发生了死锁。

2.读写锁

当有一个线程已经持有互斥锁时,互斥锁将所有试图进入临界区的线程都阻塞住。但是考虑一种情形,当前持有互斥锁的线程只是要读访问共享资源,而同时有其它几个线程也想读取这个共享资源,但是由于互斥锁的排它性,所有其它线程都无法获取锁,也就无法读访问共享资源了,但是实际上多个线程同时读访问共享资源并不会导致问题
在对数据的读写操作中,更多的是读操作,写操作较少,例如对数据库数据的读写应用。
为了满足当前能够允许多个读出但只允许一个写入的需求,线程提供了读写锁来实现。


◼ 读写锁的特点:
 如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作。
 如果有其它线程写数据,则其它线程都不允许读、写操作。
 写是独占的,写的优先级高。

如果A线程加的是读锁,然后B线程是要加写锁,C线程是要加读锁。那么由于写的优先级高,因此B先加写锁。

测试代码:

/*读写锁的类型 pthread_rwlock_tint pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);案例:8个线程操作同一个全局变量。3个线程不定时写这个全局变量,5个线程不定时的读这个全局变量
*/#include <stdio.h>
#include <pthread.h>
#include <unistd.h>// 创建一个共享数据,作为全局变量
int num = 1;// pthread_mutex_t mutex;互斥锁的效率相比于读写锁要慢
//创建读写锁
pthread_rwlock_t rwlock;void * writeNum(void * arg) {while(1) {pthread_rwlock_wrlock(&rwlock);num++;printf("++write, tid : %ld, num : %d\n", pthread_self(), num);pthread_rwlock_unlock(&rwlock);usleep(100);}return NULL;
}void * readNum(void * arg) {while(1) {pthread_rwlock_rdlock(&rwlock);printf("===read, tid : %ld, num : %d\n", pthread_self(), num);pthread_rwlock_unlock(&rwlock);usleep(100);}return NULL;
}int main() {pthread_rwlock_init(&rwlock, NULL);// 创建3个写线程,5个读线程pthread_t wtids[3], rtids[5];for(int i = 0; i < 3; i++) {pthread_create(&wtids[i], NULL, writeNum, NULL);}for(int i = 0; i < 5; i++) {pthread_create(&rtids[i], NULL, readNum, NULL);}// 设置线程分离for(int i = 0; i < 3; i++) {pthread_detach(wtids[i]);}for(int i = 0; i < 5; i++) {pthread_detach(rtids[i]);}pthread_exit(NULL);pthread_rwlock_destroy(&rwlock);return 0;
}

最后三行代码的原因是:如果destory在先,因为上面的detach运行后子线程可能没有运行完,所以可能会在子线程运行过程中destory互斥量,这样会出错,如果destory在后,那么主线程exit后也不会去destory互斥量,所以我建议上面回收子线程使用join,因为join是阻塞的,会等所有的子进程资源回收完了再继续,然后再destory互斥量,最后exit主线程。

也就是说:用pthread_join,最后,最初主线程pthread_exit(NULL)

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

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

相关文章

Java实现就医保险管理系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 科室档案模块2.2 医生档案模块2.3 预约挂号模块2.4 我的挂号模块 三、系统展示四、核心代码4.1 用户查询全部医生4.2 新增医生4.3 查询科室4.4 新增号源4.5 预约号源 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVue…

【前端素材】推荐优质后台管理系统Qovex平台模板(附源码)

一、需求分析 1、定义 后台管理系统是一种用于管理和监控网站、应用程序或系统的在线工具。它通常是通过网页界面进行访问和操作&#xff0c;用于管理网站内容、用户权限、数据分析等。后台管理系统是网站或应用程序的控制中心&#xff0c;管理员可以通过后台系统进行各种管理…

构造函数,原型,实例,类的关系整理

视频来源js原型链、构造函数和类_哔哩哔哩_bilibili 如视频所说&#xff0c;构造函数的prototype指向原型&#xff0c;实例化的对象的__proto__指向原型&#xff0c;原型通过constructor指向构造函数&#xff0c;正如class里面的constructor方法就相当于Person构造函数一样&am…

基于PID-bang-bang控制算法的卫星姿态控制matlab仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于PID-bang-bang控制算法的卫星姿态控制。仿真输出控制器的控制收敛曲线&#xff0c;卫星姿态调整过程的动画。 2.系统仿真结果 3.核心程序与模型 版本&#xff1a;MATLAB…

前后端联调可能出现的问题

调不到后端数据 前后端传参方式不一样 --- formdata 主要接收文件 或者有文件和数据 --- json 纯数据

springboot+vue项目部署配置开机自启动

1.前端部属 下载nginx解压&#xff0c;在nginx\conf下找到nginx.conf 添加如下代码 server {listen 8081;server_name localhost;charset utf-8;location / {root F:/1ceshi/dist; #前端打包路径try_files $uri $uri/ /index.html;index index.html index.htm;}l…

本地创建Git仓库

在 Windows 下&#xff0c;可以通过以下步骤在本地创建一个 并模拟远程Git 仓库。 1、在命令行中打开模拟远程Git 仓库目标文件夹&#xff1a; 打开命令提示符或 PowerShell。例如&#xff1a; 创建裸仓库&#xff08;模拟远程仓库&#xff09;&#xff1a;创建一个裸仓库&am…

Redis进阶篇

Redis线程模型 redis是基于内存运行的高性能k-v数据库&#xff0c;6.x之前是单线程, 对外提供的键值存储服务的主要流程 是单线程&#xff0c;也就是网络 IO 和数据读写是由单个线程来完成&#xff0c;6.x之后引入多线程而键值对读写命 令仍然是单线程处理的&#xff0c;所以 …

【PX4SimulinkGazebo联合仿真】在Simulink中使用ROS2控制无人机进入Offboard模式起飞悬停并在Gazebo中可视化

在Simulink中使用ROS2控制无人机进入Offboard模式起飞悬停并在Gazebo中可视化 系统架构Matlab官方例程Control a Simulated UAV Using ROS 2 and PX4 Bridge运行所需的环境配置PX4&Simulink&Gazebo联合仿真实现方法建立Simulink模型并完成基本配置整体框架各子系统实现…

一分钟 由浅入深 学会Navigation

目录 1.官网正式概念 1.1 初认知 2.导入依赖 2.1 使用navigation 2.2 safe Args插件-> 传递数据时用 3.使用Navigation 3.1 搭建初始框架 3.2 确定action箭头的属性 3.3 为Activity添加NavHostFragment控件 3.4 NavController 管理应用导航的对象 3.5 数据传递(单…

Linux——基础IO

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、C语言IO1、写文件2、读文件3、stdin & stdout & stderr 二、系统文件I/O1、写文件…

常用显示屏学习——LCD12864(含高级驱动程序)

LCD12864液晶显示屏 屏幕介绍 ① 可显示四行字符&#xff0c;每行可显示8个汉字或者16个数字和字母&#xff1b; ②可串行通信和并行通信&#xff1b; ③ 串口接口管脚信号 通信方法 &#xff08;一&#xff09;八位并行通信方法 &#xff08;二&#xff09;串行通信方法 用…

超实用,分享PostgreSQL和mysql的几点区别

前言 今天是元宵节&#xff0c;首先祝大伙元宵快乐&#xff01;上一篇文章&#xff0c;给大家讲解了一下MySQL和PostgreSQL性能上的差别。这篇文章主要是记录一下日常应用中&#xff0c;两者常见的一些语法以及一些区别。 PostgreSQL的数据类型 数值类型 字符串类型 日期|时…

Linux--shell编程中分区表常用操作 全面且详细

文章中关于分区表常用操作目录&#xff1a; 一、概念 二、​​​​​​​创建分区表语法 ​​​​​​​三、创建一个表带多个分区 四、​​​​​​​加载数据到分区表中 五、加载数据到一个多分区的表中去 ​​​​​​​六、查看分区 七、​​​​​​​添加一个分区…

机器学习:逻辑回归原理

逻辑回归模型是一种广泛应用于分类问题的统计方法。尽管名为“回归”&#xff0c;但它实际上是一种分类算法&#xff0c;主要用于预测观察对象属于某个类别的概率。逻辑回归模型特别适用于二分类问题&#xff0c;但也可以通过一些策略扩展到多分类问题。 逻辑回归的应用与优化…

让C语言代码变抽象(二)

目录 前言&#xff1a; 代码&#xff1a; 前言&#xff1a; 在今天写代码的时候&#xff0c;我又想到一个更抽象的代码。 我在写注释的时候突然想想到条件编译的东西&#xff0c;好像也能用来注释东西。 代码&#xff1a; 我们在这直接上干货 我们知道在条件编译中有一个叫…

【前端素材】推荐优质后台管理系统Acara平台模板(附源码)

一、需求分析 后台管理系统在多个层次上提供了丰富的功能和细致的管理手段&#xff0c;帮助管理员轻松管理和控制系统的各个方面。其灵活性和可扩展性使得后台管理系统成为各种网站、应用程序和系统不可或缺的管理工具。 后台管理系统是一种用于管理和控制网站、应用程序或系…

Map集合特点、遍历方式、TreeMap排序及Collections和Arrays

目录 ​编辑 一、集合框架 二、 Map集合 特点 遍历方式 HashMap与Hashtable的区别 TreeMap Collections Arrays 一、集合框架 二、 Map集合 Map集合是一种键值对的集合&#xff0c;其中每个键对应一个值。在Java中&#xff0c;Map接口定义了一种将键映射到值的数据结…

嵌入式学习-qt-Day3

嵌入式学习-qt-Day3 一、思维导图 二、作业 完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳…

「Java开发指南」MyEclipse如何支持Spring Scaffolding?(三)

在上文中&#xff08;点击这里回顾>>&#xff09;&#xff0c;主要为大家介绍了CRUD Scaffolding&#xff0c;本文将继续介绍应用程序的分层、代码助手等。 MyEclipse v2023.1.2离线版下载 3. 应用程序的分层 应用程序分层是应用程序开发领域中非常常见的体系结构方法…