C++ 死锁处理
外观
死锁(Deadlock)是多线程编程中一种常见的并发问题,指两个或多个线程因互相持有对方所需的资源而无限期阻塞的状态。本章将详细讲解C++中死锁的成因、检测方法及解决方案。
死锁简介[编辑 | 编辑源代码]
死锁的四个必要条件(Coffman条件):
- 互斥条件:资源一次只能被一个线程占用。
- 占有并等待:线程持有资源并等待其他资源。
- 非抢占条件:已分配的资源不能被强制剥夺。
- 循环等待:存在线程资源的环形等待链。
数学表达为:存在线程集合 和资源集合 ,满足 等待 且 被 持有(环形链)。
死锁示例[编辑 | 编辑源代码]
以下是一个典型的双线程死锁案例:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mutex1, mutex2;
void threadA() {
mutex1.lock();
std::cout << "Thread A acquired mutex1\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟耗时操作
mutex2.lock(); // 此处阻塞
std::cout << "Thread A acquired mutex2\n";
mutex2.unlock();
mutex1.unlock();
}
void threadB() {
mutex2.lock();
std::cout << "Thread B acquired mutex2\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mutex1.lock(); // 此处阻塞
std::cout << "Thread B acquired mutex1\n";
mutex1.unlock();
mutex2.unlock();
}
int main() {
std::thread t1(threadA);
std::thread t2(threadB);
t1.join();
t2.join();
return 0;
}
输出结果:
Thread A acquired mutex1 Thread B acquired mutex2 (程序永久挂起)
死锁检测与预防[编辑 | 编辑源代码]
检测技术[编辑 | 编辑源代码]
- 超时机制:使用
std::timed_mutex
的try_lock_for
- 层次锁:强制定义锁的获取顺序
- 工具分析:Valgrind的Helgrind插件、Clang ThreadSanitizer
预防方法[编辑 | 编辑源代码]
方法 | 实现示例 | 说明 |
---|---|---|
固定锁顺序 | 所有线程按相同顺序获取锁 | 破坏"循环等待"条件 |
锁范围缩小 | 使用{} 限制锁的作用域 |
减少持有时间 |
RAII包装器 | 使用std::lock_guard |
自动释放保证 |
修正后的安全代码:
// 强制定义锁的获取顺序:总是先锁mutex1再锁mutex2
void safeThread() {
std::scoped_lock lock(mutex1, mutex2); // C++17提供的原子锁获取
std::cout << "Thread acquired both mutexes safely\n";
}
高级处理技术[编辑 | 编辑源代码]
银行家算法[编辑 | 编辑源代码]
通过资源分配图算法预防死锁:
C++标准库工具[编辑 | 编辑源代码]
std::lock(m1, m2, ...)
:原子地获取多个锁std::unique_lock
:支持延迟锁定和所有权转移
实际应用案例[编辑 | 编辑源代码]
数据库连接池管理场景: 1. 线程A持有连接1并请求连接2 2. 线程B持有连接2并请求连接1 3. 解决方案:为连接分配优先级编号,按编号顺序获取
总结[编辑 | 编辑源代码]
- 死锁的四个必要条件必须全部满足才会发生
- 主要解决方案:破坏任一必要条件
- 最佳实践:
** 使用RAII管理锁 ** 避免嵌套锁 ** 统一锁获取顺序 ** 使用标准库提供的线程安全容器