仅用于站内搜索,没有排版格式,具体信息请跳转上方微信公众号内链接
关于死锁你能搜到很多千篇一律的资料,今天我们来讲点不一样的,那就是从内核视角看线程是如何一步步死锁的。
我们知道线程是由内核调度器决定是否运行的,调度器本质是通过线程状态来判定运行哪个线程,进程/线程状态图想必大家都了解:
只有位于就绪态(ready)的线程才有资格被调度器调度。
来看一个简单的死锁示例:
假设现在一个单核系统中只有两个线程,线程A和线程B,这两个线程都刚被创建出来,此时的内核状态是:
现在调度器决定首先调度线程A,线程A来到thread1_func拿到锁A后系统产生中断,此时内核把线程A置于就绪态后处理中断,中断处理完毕后决定调度线程B,线程B开始运行,此时的内核状态:
线程B来到thread2_func后先拿锁B,接着拿锁A时发现锁A已被占用,因此线程B来到阻塞态,此时的内核状态:
接着调度器决定运行处于就绪态的线程A,线程A继续运行去拿锁B,但发现锁已被占用,因此线程A也来到阻塞态,此时的内核状态:
现在所有线程来到了阻塞态,注意看此时的就绪队列是空的,无就绪线程可供调度,内核调度器认为此时没有任何一个线程需要分配CPU,此时CPU来到空闲状态。
死锁的本质在于调度器只会去调度位于就绪态的线程,而位于阻塞状态的线程因为资源依赖关系无法改变线程状态,从而形成僵局,但此时在内核看来系统一切正常,如果有个新的线程还可以继续运行。
内核对位于阻塞态的线程A和线程B是视而不见的,因为内核调度器仅关注线程状态,不跟踪资源依赖关系。
你可能会问既然调度器知道线程的状态,为什么内核不能主动检测死锁?
因为调度器仅跟踪线程的执行状态(如运行、就绪、阻塞),不维护资源(如锁、信号量)的分配关系,死锁涉及资源分配以及跟踪策略,这部分通常由用户态应用程序自己处理。而且如果让内核去检测所有线程的资源依赖的话开销过高。
数据库系统是最典型的自带死锁检测机制的系统,由于数据库需要处理高并发事务和共享资源(如数据行、表、索引),死锁检测是事务管理的关键功能,这通常基于等待图(Wait-forGraph)算法,周期性地检查事务间的等待关系,发现循环等待则判定为死锁。
最后推荐一下我写的专栏《深入理解操作系统》,第二版焕新升级,600+精美手绘图、87节精讲,如果你对操作系统感兴趣并且喜欢这篇文章的风格,那么这个专栏就是为你准备的:
操作系统是如何一步步发明进程、线程的?
操作系统是如何一步步发明中断机制的?
操作系统是如何一步步发明系统调用的?