【Linux】死锁 | 条件变量部分理解

2023-10-17 08:58:12 浏览数 (1)

1. 死锁

概念

指一组进程中的各个进程均占有不会释放的资源, 但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待的状态


有两个小朋友,张三和李四,共同去了一家商店,想要向老板 购买一块价值1块钱的棒棒糖,但是他们俩各个都只有5毛钱 所以张三就跟李四说,想要李四手里的5毛钱去买棒棒糖,张三想吃 李四同样跟张三说, 想要张三手里的5毛钱去买棒棒糖, 李四想吃 两个人发生争执,手里的5毛钱互不相让,想吃棒棒糖这件事情就一直没有被执行


两个小朋友可以看作是两个线程,两个不同的小朋友可以看作两把不同的锁 棒棒糖称为 临界资源,老板 为操作系统 想要访问临界资源,必须同时拥有两把锁


两个线程各自持有自己的锁,并向对方申请锁,从而导致互相申请锁不成功,进而导致双执行流互相被挂起 访问临界资源的临界区代码,无法得以推进

死锁的必要条件

1.互斥: 一个资源每次只能被一个执行流使用

2. 请求与保持:一个执行流因请求而阻塞时,对已获得的资源保持不放 (张三向李四 要5毛钱 即请求 , 我的5毛钱不能给你,不能释放自己的5毛钱 即保持)


3. 循环等待:若干执行流之间形成一种头尾相接的循环等待资源的关系

张三向李四要5毛钱 ,同时不释放自己的5毛钱 李四向张三 要5毛钱,同时不释放自己的5毛钱 两者形成环路, 当张三不给李四时,张三进行等待 当李四不给张三时,李四进行等待


4. 不剥夺: 一个执行流已获得的资源,在未使用完之前,不能强行剥夺

假设张三的块头比李四大,若李四不给属于他自己的5毛钱,张三就要揍李四,把李四的5毛钱枪过来 就不会有死锁问题了,所以要求不能打人抢钱

如何避免死锁?

核心思想:破坏死锁的4个必要条件的任意一个


1. 不加锁


2. 主动释放锁 假设要有两把锁才能获取临界资源,本身有一把锁,在多次申请另一把锁时申请不到,就把自身的锁释放掉


3. 按照顺序申请锁 假设有线程A和B,线程A申请锁时,必须保持先A再B,线程B申请锁时,也必须保持先A再B 当线程A申请到A锁时,线程B也申请到A,就不会出现互相申请的情况了


4. 控制线程统一释放锁 将所有线程 申请的锁 使用一个线程 全部释放掉,就不会出现死锁了

证明 一个线程申请一把锁,可以由另一个线程释放

设置一个全局锁mutex,再自定义函数中由于两次申请锁,所以在第二次申请锁时,线程自己把自己挂起了 在主线程 设置解锁 ,看是否能帮助新线程中的锁进行 解锁 操作


运行可执行程序后,打印出i alive again,新线程由挂起状态活过来了 说明一个线程申请一把锁,可以由另一个线程释放

2. 线程同步

概念

教室每次只允许一个人进入去学习, 假设同学A早上起来,就来到教室拿到钥匙,通过钥匙打开门进入自习室,同时将门锁上 过了一段时间,又来了一大堆人,由于教室门锁上了,所以都在教室外等待

同学A学了一会后不想在继续学了,所以打开门,把钥匙放在墙上 但是当把钥匙挂到墙上时,又突然想继续学一会,所以同学A又把钥匙拿到了 在打开门,进入教室继续学习 又学了几分钟,又不想学了,所以一直重复上述动作,将钥匙挂墙上,再次拿到钥匙进入教室


长时间在做 拿钥匙 放钥匙 的工作 虽然没问题,但这样做不合理


你一天在临界资源中什么都没做,就是在申请锁 、释放锁, 导致别人长时间无法申请锁,进一步导致别人无法进入临界资源,导致别人的饥饿问题

所以在教室的使用规则加入一条,自习完毕的人,归还完钥匙,不能立即申请,在外面等待的人,必须排队

为了合理解决饥饿问题,在安全的规则下,多线程访问资源具有一定的顺序性,即线程同步 让多线程协同工作

条件变量

概念

当一个线程互斥访问某个变量时,它可能发现在其他线程改变状态之前,它什么也做不了


在之前的抢票机制中,都是先进行条件判断的,若票数tickets值小于等于0,则当前线程什么都做不了 按照上述的代码,先申请锁,若票数小于等于0,进入else 解锁,由于并不知道什么时候放票, 所以这个线程又会回去 申请锁,再次进行if判断,进入 else 解锁 一直重复该工作,不断进行申请锁 解锁

所以 在当前票数状态 改变之前, 无法抢票,什么都做不了


当票数 不满足条件时,就应该将线程在 条件变量中 休眠,防止 不断申请锁 释放锁 当票数满足条件满足时,在将对应的线程唤醒

接口

pthread_cond_init ——初始化

输入 man pthread_cond_init



若将条件变量其定义为全局变量,则可以使用PTHREAD_COND_INITIALIZER 进行初始化 就不用再主函数中使用 初始化 和销毁 了

若条件变量定义为局部变量 ,就必须在主函数中调用 pthread_cond_init 和 pthread_cond_destroy

pthread_cond_destroy ——销毁

当条件变量为局部变量时,与pthread_cond_init 一起配合使用

pthread_cond_wait —— 等待条件满足

输入 man pthread_cond_wait

当前线程 检测 的对应的临界资源条件不满足,就进入等待状态

第一个参数为 要在那个条件变量中等待 第二个参数为 互斥锁


为什么互斥锁作为参数?

判断条件是否满足,本身就是访问临界资源的行为 ,所以要在加锁之后

若条件不满足,直接让该线程休眠,是没有机会释放锁的


所以在pthread_cond_wait 中 第二个参数是锁 在调用 pthread_cond_wait 除了 可以把线程在条件变量下等待,当前函数也能自动释放曾经持有的锁

唤醒

输入 man pthread_cond_signal

pthread_cond_signal 代表 唤醒该条件变量下等待的线程 pthread_cond_broadcast 代表 将全部的线程唤醒

基本用法

创建5个线程, 通过调用snprintf 将 pthread-%d 字符串传入name中, name作为回调函数的参数args


定义一个全局锁,所以不用在主函数中使用init和destroy 先加锁,默认判断条件不满足,所以使用 wait ,将线程在条件变量中休眠,并自动释放锁 当被唤醒后,会继续向后运行 打印出活动


在主函数中唤醒 该条件变量下等待的线程



当主线程每唤醒一个线程,就会打印出对应的活动 5 1 2 3 4 ,在条件变量下进行排队 即让所有线程按顺序执行

0 人点赞