[ Linux ] 死锁以及如何避免死锁

时间:2022-12-23 14:13:30

1.什么是死锁?

死锁

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

2.模拟死锁情况

我们使用多线程模拟一个死锁的情况。

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <mutex>

using namespace std;

pthread_mutex_t mutexA = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutexB = PTHREAD_MUTEX_INITIALIZER;
void* startRoutine1(void* args)
{
while(true)
{
pthread_mutex_lock(&mutexA);
sleep(1);
pthread_mutex_lock(&mutexB);
cout<<"我是线程1,我的tid"<<pthread_self()<<endl;
pthread_mutex_unlock(&mutexB);
pthread_mutex_unlock(&mutexA);
}

}

void* startRoutine2(void* args)
{
while(true)
{
pthread_mutex_lock(&mutexB);
sleep(1);
pthread_mutex_lock(&mutexA);
cout<<"我是线程2,我的tid"<<pthread_self()<<endl;
pthread_mutex_unlock(&mutexB);
pthread_mutex_unlock(&mutexA);
}
}


int main()
{
pthread_t t1,t2;

pthread_create(&t1,nullptr,startRoutine1,nullptr);
pthread_create(&t2,nullptr,startRoutine2,nullptr);

pthread_join(t1,nullptr);
pthread_join(t2,nullptr);
return 0;
}

当上面这段程序运行起来时,线程1和线程2会同时调用各自的startRountine函数,由于mutexA和mutexB均为临界资源只有一份,而在程序中,线程1会先申请mutexA,线程2会先申请mutexB,而当他们申请结束时都会等待1秒(这里等待一秒是确保两个线程各自占有一把锁),一秒休眠结束后,当线程1想要申请mutexB时,由于mutexB已经被线程2占有且未释放,因此线程1将阻塞等待mutexB的释放;同理,线程2也会阻塞等待mutexA。此时,各线程均占有不会释放的资源mutex,并且互相申请了其他线程所占用不会释放的资源而除以一种永久等待的状态。我们把这种状态就称为死锁。当程序运行起来时,我们也能看到程序并没有什么打印结果.....

我们使用监控脚本查看3个线程任然在运行之中

while :; do ps -aL | grep mythread ; sleep 1;echo "---------------";done


3.死锁四个必要条件

死锁的必要条件:死锁情况一旦发生,这四个条件一定都要满足。如果有一个没有产生,那死锁的条件便不成立。

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


4.避免死锁的方法

  • 破坏死锁的四个必要条件
  • 加锁顺序一致
  • 避免锁未释放的场景
  • 资源一次性分配


5.避免死锁的算法

银行家算法(了解为主)

我们在当时将锁的时候,查看过pthread_mutex_lock和pthread_mutex_unlock函数,我们也使用了,那么大家还记不记得其中还有一个trylock。这个函数就是用来尝试申请,如果是安全序列则会正式分配,否则不会分配。

核心思想:

银行家算法是一种最有代表性的避免死锁的算法。在避免死锁方法中允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次分配资源的安全性,若分配不会导致系统进入不安全状态,则分配,否则等待