Administrator
发布于 2026-06-25 / 0 阅读
0
0

字节一面说说你了解的死锁包括死锁产生原因必要条件处理方法死锁回复以及死锁预防等死锁相关问题大总结超全

字节一面:说说你了解的死锁?包括死锁产生原因、必要条件、处理方法、死锁回复以及死锁预防等(死锁相关问题大总结,超全!)

原创 程序员阿沛 程序员阿沛 [ 程序员阿沛 ](javascript:void(0);)

本文归于合集: [ 吊打面试官系列

](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxMjczODMyMA==&action=getalbum&album_id=3544175153230741505#wechat_redirect)

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。

** 面试官:说说你了解的死锁,和死锁产生原因。 **

举个例子:两个线程A和B,两个数据1和2。线程A在执行过程中,首先对资源1加锁,然后再去给资源2加锁,但是由于线程的切换,导致线程A没能给资源2加锁。线程切换到B后,线程B先对资源2加锁,然后再去给资源1加锁,由于资源1已经被线程A加锁,因此线程B无法加锁成功,当线程切换为A时,A也无法成功对资源2加锁,由此就造成了线程AB双方相互对一个已加锁资源的等待,死锁产生。

理论上认为死锁产生有以下四个必要条件,缺一不可:

a. 互斥条件

进程对所分配到的资源进行排他性使用,即在一段时间内,某资源只能被一个进程占用。
如果此时还有其他进程请求该资源,则请求进程只能等待,直至占有该资源的进程释放。

b.请求与保持条件

进程已经保持至少一个资源,又请求新的资源而失败。 此时请求进程被阻塞,但对自己已获得的资源保持不放。

c. 不可抢占条件

进程已获得的资源在未使用完之前不能被抢占,只能在进程使用完时自己释放。

d. 循环等待条件

若干进程之间形成一种头尾相接的循环等待资源关系。

从资源竞争的角度来说,又可以分为 不可抢占资源竞争 和 可消耗资源竞争两种情况。

a. 不可抢占资源竞争

系统中所拥有的不可抢占性资源(如磁带机、打印机等)数量不足以满足多个进程运行的需要。 进程在运行过程中,会因争夺这些资源而陷入僵局。

b.可消耗资源竞争:

可消耗资源(如硬件中断、信号、消息、缓冲区内的消息等)也可能引起死锁。 这些资源由一个进程产生,被另一个进程使用,短时间后便无用。

产生死锁的具体原因如下。

1. 系统资源有限

当系统资源数量有限时,多个进程对资源的竞争更加激烈,更容易产生死锁。

2.资源分配策略不当

如果系统的资源分配策略不合理,也可能导致进程因竞争资源不当而产生死锁。

3.程序员编写的程序错误:

程序员在编写程序时,如果对资源的使用和释放处理不当,也可能导致死锁的发生。

** 面试官:请你写一段代码模拟一下死锁的发生。 **

** **

通过代码的形式进行演示,需要两个线程和两个互斥量。


#include <iostream>#include <vector>#include <list>#include <thread>#include <mutex>  //引入互斥量头文件using namespace std;  
class A {public:  //插入消息,模拟消息不断产生  void insertMsg() {    for (int i = 0; i < 100; i++) {      cout << "插入一条消息:" << i << endl;      my_mutex1.lock(); //语句1      my_mutex2.lock(); //语句2      Msg.push_back(i);      my_mutex2.unlock();      my_mutex1.unlock();    }  }  //读取消息  void readMsg() {    int MsgCom;    for (int i = 0; i < 100; i++) {      MsgCom = MsgLULProc(i);      if (MsgLULProc(MsgCom)) {        //读出消息了        cout << "消息已读出" << MsgCom << endl;      }      else {        //消息暂时为空        cout << "消息为空" << endl;      }    }  }  //加解锁代码  bool MsgLULProc(int &command) {    int curMsg;    my_mutex2.lock();   //语句3    my_mutex1.lock();   //语句4    if (!Msg.empty()) {      //读取消息,读完删除      command = Msg.front();      Msg.pop_front();  
      my_mutex1.unlock();      my_mutex2.unlock();      return true;    }    my_mutex1.unlock();    my_mutex2.unlock();    return false;  }private:  std::list<int> Msg;  //消息变量  std::mutex my_mutex1; //互斥量对象1  std::mutex my_mutex2; //互斥量对象2};  
int main() {  A a;  //创建一个插入消息线程  std::thread insertTd(&A::insertMsg, &a); //这里要传入引用保证是同一个对象  //创建一个读取消息线程  std::thread readTd(&A::readMsg, &a); //这里要传入引用保证是同一个对象  insertTd.join();  readTd.join();  return 0;}

语句1和语句2表示线程A先锁资源1,再锁资源2,语句3和语句4表示线程B先锁资源2再锁资源1,具备死锁产生的条件。

** 面试官:那么死锁该如何解决? **

可以从预防死锁、避免死锁、死锁检测、死锁恢复来解决。

a. 预防死锁

1. 破坏循环等待条件

锁排序:通过对加锁的操作进行排序,可以避免循环等待的发生。例如,在多线程编程中,可以按照资源在数组中的位置顺序,或数据库中表的顺序进行加锁操作。

资源有序分配法: 给系统中的资源编号,所有线程申请资源时必须按编号递增的顺序提出请求,这样就不会出现循环等待的情况。

2. 破坏请求保持条件

一次性分配所有资源:线程在运行前一次性申请完它所需要的全部资源,若所需全部资源得不到满足,则不分配任何资源,线程暂不运行。这种方式虽然简单,但可能导致资源利用率降低,因为有些资源可能在线程的整个生命周期内都不会被使用。

持有并等待的线程主动释放资源: 如果一个线程已经持有了一些资源,但在尝试获取更多资源时失败了,它可以选择主动释放自己已经持有的资源,并在稍后重试。
然而,这种方法可能导致活锁问题,即线程不断地放弃和重试,导致系统整体性能下降。 为了避免活锁,可以在重试时引入随机延迟。

允许锁抢占:即允许一个线程在持有某个资源的同时,被另一个线程抢占该资源。这种方式需要谨慎实现,因为它可能导致数据不一致或系统不稳定。

b. 避免死锁

银行家算法:这是一种经典的避免死锁算法,通过模拟资源分配过程来预测是否会发生死锁,从而避免死锁的发生。然而,银行家算法在实际应用中较为复杂,且需要系统状态的完整信息,因此并不总是可行的。

资源分配图算法:通过构建资源分配图来监测系统的资源分配情况,当检测到可能形成环路等待时,采取预防措施来避免死锁。这种方法同样需要系统状态的完整信息,并且可能增加系统的开销。

c. 死锁检测

通过定期检测系统的状态来确定是否发生了死锁。一旦检测到死锁,系统可以采取恢复措施。

超时法: 为每个线程设置一个超时时间,如果线程在超时时间内未能获得所需的全部资源,则认为发生了死锁。
资源分配图检测法: 利用资源分配图来检测是否形成了环路等待。
d.死锁恢复 终止进程: 选择一个或多个陷入死锁的线程,终止它们的执行。 这可能会导致数据丢失或服务中断,因此需要谨慎选择被终止的线程。 回滚:
将系统恢复到某个安全的状态点,通常是通过回滚事务来实现的。 这种方法可以保持数据的一致性,但可能需要额外的开销来维护事务的回滚点。 资源抢占:
从陷入死锁的线程中抢占一些资源,并分配给其他线程。 这种方法需要确保不会破坏系统的稳定性和数据的一致性。

e. 其他技术

使用乐观锁:乐观锁通常基于数据版本记录机制实现,通过比较数据版本号来避免冲突。这种方法适用于并发量较高且冲突较少的场景。

使用悲观锁:悲观锁大多数情况下依靠数据库的锁机制实现,以确保操作最大程度的独占性。但可能会带来较大的性能开销。

无锁数据结构:通过原子操作来实现无锁数据结构,从而避免互斥锁所带来的开销和复杂性。这种方法适用于一些特定的场景,如并发队列、哈希表等。


欢迎在评论区留言表达看法 或 提出你想学习的技术内容 与 面试问题,阿沛会将其作为下期更新的内容。

如果本文对大家有帮助,麻烦大家动动小手点个免费的“赞”或“在看”,大家的鼓励就是阿沛持续更新的动力~

** -- 往期精彩回顾 – **

面试题:CAS的全称是什么?它的基本原理是什么?ABA问题如何解决?

深入Redis系列(七)Redis事务原理详解

MySQL怎么运行的系列(九)事务隔离级别和MVCC原理

“我的领导只任用关系户,我的团队里全是嫡系”

面试官:HTTPS 采用的是对称加密还是非对称加密?具体说说其加密过程


评论