跳转到内容

C++ 死锁处理

来自代码酷


死锁(Deadlock)是多线程编程中一种常见的并发问题,指两个或多个线程因互相持有对方所需的资源而无限期阻塞的状态。本章将详细讲解C++中死锁的成因、检测方法及解决方案。

死锁简介[编辑 | 编辑源代码]

死锁的四个必要条件(Coffman条件):

  1. 互斥条件:资源一次只能被一个线程占用。
  2. 占有并等待:线程持有资源并等待其他资源。
  3. 非抢占条件:已分配的资源不能被强制剥夺。
  4. 循环等待:存在线程资源的环形等待链。

数学表达为:存在线程集合 T1,T2,...,Tn 和资源集合 R1,R2,...,Rm,满足 Ti 等待 RjRjTi+1 持有(环形链)。

死锁示例[编辑 | 编辑源代码]

以下是一个典型的双线程死锁案例:

#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
(程序永久挂起)

graph LR A[ThreadA: 持有mutex1] -->|等待mutex2| B[ThreadB: 持有mutex2] B -->|等待mutex1| A

死锁检测与预防[编辑 | 编辑源代码]

检测技术[编辑 | 编辑源代码]

  • 超时机制:使用std::timed_mutextry_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";
}

高级处理技术[编辑 | 编辑源代码]

银行家算法[编辑 | 编辑源代码]

通过资源分配图算法预防死锁: 安全状态安全序列T1,T2,...,Tn

C++标准库工具[编辑 | 编辑源代码]

  • std::lock(m1, m2, ...):原子地获取多个锁
  • std::unique_lock:支持延迟锁定和所有权转移

实际应用案例[编辑 | 编辑源代码]

数据库连接池管理场景: 1. 线程A持有连接1并请求连接2 2. 线程B持有连接2并请求连接1 3. 解决方案:为连接分配优先级编号,按编号顺序获取

总结[编辑 | 编辑源代码]

  • 死锁的四个必要条件必须全部满足才会发生
  • 主要解决方案:破坏任一必要条件
  • 最佳实践:
 ** 使用RAII管理锁
 ** 避免嵌套锁
 ** 统一锁获取顺序
 ** 使用标准库提供的线程安全容器

模板:C++多线程导航