文章目录
- 死锁
- 关于阻塞的理解
- 死锁的四个必要条件
- 避免死锁的方法
死锁
死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态
单执行流可能导致死锁问题吗?
可能!例如:某一个执行流连续申请了两次锁,那么此时该执行流就会被挂起
原因:
- 因为该执行流第一次申请锁的时候是申请成功的,但第二次申请锁时因为该锁已经被申请过了,于是申请失败导致被挂起直到该锁被释放时才会被唤醒
- 但是这个锁本来就在自己(该执行流)手上,自己现在处于被挂起的状态根本没有机会释放锁,所以该执行流将永远不会被唤醒,此时该执行流也就处于一种死锁的状态
证明:
我们让主线程创建的新线程连续申请了两次锁
#include<stdio.h>
#include<pthread.h>
pthread_mutex_t mut;//互斥锁
void* Routine(void* args)
{
pthread_mutex_lock(&mut);
pthread_mutex_lock(&mut);
}
int main()
{
pthread_t tid;
pthread_mutex_init(&mut,NULL);//初始化互斥锁
pthread_create(&tid,NULL,Routine,NULL);//创建新线程
pthread_join(tid,NULL);//等待新线程
pthread_mutex_destroy(&mut);//释放互斥锁
return 0;
}
此时该程序实际就处于一种被挂起的状态
用ps
命令查看该进程时可以看到,该进程当前的状态是Sl+
,其中的l
实际上就是lock的意思,表示该进程当前处于一种死锁的状态
关于阻塞的理解
什么是阻塞?
进程运行时是被CPU调度的,换句话说进程在调度时是需要用到CPU资源的,每个CPU都有一个运行等待队列,CPU在运行时就是从等待队列中获取进程进行调度的
在运行等待队列中的进程本质上就是在等待CPU资源,实际上不止是等待CPU资源如此,等待其他资源也是如此,比如锁的资源、磁盘的资源、网卡的资源等等,它们都有各自对应的资源等待队列
情形:当某一个进程在被CPU调度时,该进程需要用到锁的资源,但是此时锁的资源正在被其他进程使用
1)此时该进程的状态就会由R状态变为某种阻塞状态(例如:S状态),并且该进程会被移出运行等待队列,被链接到等待锁的资源的资源等待队列,而CPU则继续调度运行等待队列中的下一个进程
2)此后若还有进程需要用到这一个锁的资源,那么这些进程也都会被移出运行等待队列,依次链接到这个锁的资源等待队列当中
3)直到使用锁的进程已经使用完毕,也就是锁的资源已经就绪,此时就会从锁的资源等待队列中唤醒一个进程,将该进程的状态由S状态改为R状态,并将其重新链接到运行等待队列,等到CPU再次调度该进程时,该进程就可以使用到锁的资源了
总结:
- 站在操作系统的角度,进程等待某种资源,就是将当前进程的task_struct放入对应的等待队列,这种情况可以称之为当前进程被挂起等待了
- 站在用户角度,当进程等待某种资源时,用户看到的就是自己的进程卡住不动了,我们一般称之为应用阻塞了
- 这里所说的资源可以是硬件资源也可以是软件资源,锁本质就是一种软件资源,当我们申请锁时,锁当前可能并没有就绪,可能正在被其他线程所占用,此时当其他线程再来申请锁时,就会被放到这个锁的资源等待队列当中
死锁的四个必要条件
- 互斥条件: 一个资源每次只能被一个执行流使用
- 请求与保持条件: 一个执行流因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件: 一个执行流已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件: 若干执行流之间形成一种头尾相接的循环等待资源的关系
注意: 这是死锁的四个必要条件,也就是说只有同时满足了这四个条件才可能产生死锁
避免死锁的方法
- 破坏死锁的四个必要条件
- 加锁顺序一致
- 避免锁未释放的场景
- 资源一次性分配
除此之外,还有一些避免死锁的算法,比如死锁检测算法和银行家算法