欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 能源 > 【Linux】【进程】死锁

【Linux】【进程】死锁

2025/2/13 22:00:45 来源:https://blog.csdn.net/m0_64014551/article/details/145599214  浏览:    关键词:【Linux】【进程】死锁

【Linux】【进程】死锁

死锁 多个线程/进程 之间并行执行 竞争访问共享资源 添加互斥锁 但是由于互斥锁设计不当,导致多个线程或进程形成了“相互等待”的关系。

1. 死锁产生的必要条件

  1. 互斥条件

    • 多个线程不能同时使用同一个资源。
  2. 持有并等待

    • 线程或进程至少持有一个资源,并且在等待获取其他被其他线程或进程占用的资源。
  3. 不可剥夺

    • 已分配的资源在未使用完之前,不能被强制剥夺,只能由占有该资源的线程或进程主动释放。
  4. 环路等待

    • 存在一个线程或进程的环形链,每个线程/进程都在等待链中下一个线程/进程所持有的资源。

只有同时满足这四个条件时,死锁才可能发生。


2. 死锁的产生原因

在 Linux 进程或多线程程序中,常见的死锁产生原因包括:

  • 资源获取顺序不一致
    当多个线程或进程在申请多个资源时,如果获取资源的顺序不一致,容易形成环路等待(循环等待)。例如,线程 A 先获取资源 1,再等待资源 2;而线程 B 先获取资源 2,再等待资源 1,就会产生死锁。

  • 长时间持有锁
    如果某个线程在持有锁后执行了耗时操作,其他等待该锁的线程可能会长时间阻塞,增加形成死锁的风险。

  • 嵌套锁和递归锁
    当多个锁之间存在嵌套依赖关系时,如果设计不当,容易出现循环等待。

  • 不当使用条件变量
    使用条件变量时,如果等待和通知机制设计不合理,也可能导致某些线程永远等待,从而形成死锁。


3. 如何预防和避免死锁

为了避免死锁,可以采用以下策略:

  1. 资源获取顺序统一

    • 规定所有线程或进程必须按照相同的顺序申请资源,从而避免环路等待(循环等待)。
  2. 使用定时锁尝试机制

    • 使用带超时机制的锁(如 pthread_mutex_timedlock),在获取锁失败后释放已持有的资源,并重试。
  3. 减少锁的持有时间

    • 尽量缩短持有锁的时间,避免在持有锁期间执行耗时操作。
  4. 死锁检测与恢复

    • 部分系统可以实现死锁检测机制,在发现死锁时终止其中一个或多个线程来恢复系统。

4. 示例代码

下面是一个简单的死锁示例,展示了由于资源获取顺序不一致而导致死锁的情况:

#include <iostream>
#include <thread>
#include <mutex>std::mutex mutex1;
std::mutex mutex2;void threadFunc1() {std::lock_guard<std::mutex> lock1(mutex1);std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟操作耗时std::lock_guard<std::mutex> lock2(mutex2);std::cout << "Thread 1 acquired both locks." << std::endl;
}void threadFunc2() {std::lock_guard<std::mutex> lock2(mutex2);std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟操作耗时std::lock_guard<std::mutex> lock1(mutex1);std::cout << "Thread 2 acquired both locks." << std::endl;
}int main() {std::thread t1(threadFunc1);std::thread t2(threadFunc2);t1.join();t2.join();return 0;
}

分析:

  • 线程 1:先获取 mutex1,然后等待获取 mutex2
  • 线程 2:先获取 mutex2,然后等待获取 mutex1
  • 两个线程之间形成了循环等待,最终导致死锁。

5. 解决死锁的示例

为了解决上述死锁问题,可以统一资源申请顺序。例如,让两个线程都按照相同的顺序(先获取 mutex1 再获取 mutex2)申请锁:

#include <iostream>
#include <thread>
#include <mutex>std::mutex mutex1;
std::mutex mutex2;void safeThreadFunc() {std::lock_guard<std::mutex> lock1(mutex1);std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟操作耗时std::lock_guard<std::mutex> lock2(mutex2);std::cout << "Thread acquired both locks safely." << std::endl;
}int main() {std::thread t1(safeThreadFunc);std::thread t2(safeThreadFunc);t1.join();t2.join();return 0;
}

这样做的好处是:

  • 两个线程都按相同顺序获取锁,避免了循环等待,死锁问题得以解决。

排查方式

在 Linux 下,定位死锁问题时,可以结合使用 pstack 和 gdb 工具来帮助排查和调试。

1. 使用 pstack 查看线程调用栈

pstack 是一个方便的工具,用于打印出一个进程中所有线程的调用栈

  • 使用方法
    • 假设进程 ID 为 PID,可以运行:
      pstack PID
      
    • pstack 会打印出该进程中所有线程的堆栈信息。你可以观察到哪些线程在阻塞等待锁,以及他们的调用路径,判断是否存在死锁现象。

2. 使用 gdb 进行深入调试

gdb 提供了更强大的交互式调试功能,可以帮助你:

  • 附加到运行中的进程
    • 使用命令:
      gdb -p PID
      
    • 进入 gdb 后,可以使用 info threads 命令查看所有线程的状态。

3. 总结

  • pstack:用于快速获取所有线程的调用栈,帮助初步定位哪些线程可能陷入等待状态。
  • gdb:用于深入调试,可以附加到运行中的进程,通过查看各线程的详细堆栈信息、局部变量及锁状态,帮助你确定死锁的根源。
  • 结合这两种工具,可以有效定位和解决 Linux 进程中的死锁问题,从而提高系统的稳定性和性能。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com