面试题答案
一键面试定位死锁的步骤
- 日志记录: 在获取和释放互斥锁的关键代码位置添加详细日志,记录线程ID、获取/释放锁的时间和锁的标识。通过分析日志可以发现线程获取锁的顺序和等待情况,初步定位死锁发生的位置。
- 使用工具:
- 调试器:在支持多线程调试的IDE(如Visual Studio、Intellij IDEA等)中,使用调试功能暂停程序执行,查看线程堆栈信息。死锁的线程通常会在获取锁的地方阻塞,通过分析堆栈能找到相关代码。
- 系统工具:在Linux系统中,
pstack
命令可以查看进程中所有线程的堆栈信息;strace
命令可以跟踪系统调用,帮助发现线程在锁相关系统调用(如pthread_mutex_lock
)上的阻塞。
- 死锁检测算法: 实现死锁检测算法,如资源分配图算法。维护一个资源分配图,图中节点表示线程和资源,边表示资源分配关系和请求关系。定期检查图中是否存在环,若存在环则表示可能发生死锁,环中的线程即为死锁线程。
避免死锁的策略及实现要点
- 破坏死锁的四个必要条件
- 破坏互斥条件:
- 策略:尽量使用可共享的资源,减少互斥资源的使用。例如,将某些只读数据设置为共享访问,无需加锁。
- 实现要点:确保共享资源的读操作不会被其他线程修改,在代码中明确区分只读和读写操作,只读操作无需获取互斥锁。
- 破坏占有并等待条件:
- 策略:要求线程一次性获取所有需要的资源,而不是逐步获取。
- 实现要点:在程序设计阶段,分析每个线程所需的所有资源,在启动线程前,让线程尝试一次性获取这些资源。若有任何资源获取失败,则释放已获取的所有资源并等待一段时间后重试。
- 破坏不可剥夺条件:
- 策略:允许系统在必要时剥夺线程已经获得的资源。
- 实现要点:为每个资源设置一个优先级,当一个高优先级线程需要某个被低优先级线程占用的资源时,低优先级线程必须释放该资源。在代码中,通过引入资源优先级管理机制,在获取锁的逻辑中加入优先级判断和资源剥夺逻辑。
- 破坏循环等待条件:
- 策略:对资源进行排序,规定线程只能按照资源的顺序获取锁,避免形成循环等待。
- 实现要点:为每个互斥锁分配一个唯一的标识符,按照标识符从小到大或其他固定顺序获取锁。在代码中,获取锁的函数需要确保按照既定顺序获取锁,如先获取标识符小的锁,再获取标识符大的锁。
- 破坏互斥条件:
- 使用超时机制
- 策略:在线程获取锁时设置一个超时时间,如果在规定时间内未能获取到锁,则放弃获取并释放已获取的其他锁,等待一段时间后重试。
- 实现要点:在互斥锁的获取函数(如
pthread_mutex_timedlock
)中设置合适的超时时间。同时,在代码中要处理好获取锁超时后的逻辑,包括释放已获取的锁和重试机制,避免无限重试导致的性能问题。
- 使用资源分配图算法
- 策略:在每次资源分配前,使用资源分配图算法检查是否会导致死锁,如果会导致死锁则拒绝分配。
- 实现要点:实现资源分配图的数据结构,在每次资源请求时,模拟资源分配并检查资源分配图是否存在环。若存在环,则拒绝分配;若不存在环,则进行实际的资源分配。在代码中,要确保资源分配图的实时更新和准确检查。