文章目录
一、死锁介绍
死锁是指两个或多个进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,导致所有涉及的进程都无法继续执行下去。比如有两个线程,都需要同时锁住两个互斥,才可以进行某项操作,但它们分别都只锁住了一个互斥,都等着再给另一个互斥加锁。于是,双方毫无进展,因为它们同在苦苦等待对方解锁互斥。
一个经典的比喻:哲学家就餐问题
5 位哲学家围坐圆桌,每人左右各有一根筷子,吃饭需要同时拿起左右两根筷子,如果每个人都先拿左边的筷子,再等右边的,就会全部卡住,无人能吃饭,引发死锁。
产生死锁的四个条件
必须同时满足以下四点,才会发生死锁:
| 条件 | 说明 | 是否可破坏 |
|---|---|---|
| 1. 互斥条件(Mutual Exclusion) | 资源一次只能被一个进程使用(如打印机、mutex) | ❌ 通常不可破坏(资源本质决定) |
| 2. 占有并等待(Hold and Wait) | 进程已持有至少一个资源,同时等待其他被占用的资源 | ✅ 可破坏 |
| 3. 非抢占条件(No Preemption) | 已分配的资源不能被强制收回,只能由持有者主动释放 | ✅ 可破坏 |
| 4. 循环等待(Circular Wait) | 存在一个进程等待环:P₁→P₂→...→Pₙ→P₁ | ✅ 可破坏 |
c++代码示例:
cpp
#include <thread>
#include <mutex>
std::mutex mtx1, mtx2;
void thread1() {
std::lock_guard<std::mutex> lock1(mtx1); // 先锁 mtx1
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::lock_guard<std::mutex> lock2(mtx2); // 再锁 mtx2
}
void thread2() {
std::lock_guard<std::mutex> lock2(mtx2); // 先锁 mtx2
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::lock_guard<std::mutex> lock1(mtx1); // 再锁 mtx1
}
int main() {
std::thread t1(thread1);
std::thread t2(thread2);
t1.join();
t2.join(); // 程序可能永远卡住!
}
再比如,假设有两个账户 A 和 B,两个线程同时进行转账:
- 线程1:从 A 转账到 B
- 线程2:从 B 转账到 A
- 每个账户有一个自己的 mutex 保护余额。
如果各自先锁"源账户",再锁"目标账户",就可能死锁:
cpp
// 危险代码:可能导致死锁
class Account {
public:
int balance;
mutable std::mutex m;
Account(int b) : balance(b) {}
};
void transfer(Account& from, Account& to, int amount) {
std::lock_guard<std::mutex> lock1(from.m); // 先锁 from
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // 模拟延迟
std::lock_guard<std::mutex> lock2(to.m); // 再锁 to
from.balance -= amount;
to.balance += amount;
}
二、死锁防治方法
方法1:统一加锁顺序
上述转账例子,使用 std::lock() + std::adopt_lock来原子地锁定多个互斥量),从而彻底避免死锁
cpp
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
class Account {
public:
mutable std::mutex m; // 每个账户一个 mutex
int balance;
Account(int initial_balance) : balance(initial_balance) {}
};
void transfer(Account& from, Account& to, int amount) {
// 关键:使用 std::lock 原子地锁定多个 mutex
std::lock(from.m, to.m); // 无死锁风险!
// 使用 adopt_lock 告诉 lock_guard:锁已经 acquired
std::lock_guard<std::mutex> lock_from(from.m, std::adopt_lock);
std::lock_guard<std::mutex> lock_to(to.m, std::adopt_lock);
// 执行转账(临界区)
from.balance -= amount;
to.balance += amount;
// lock_guard 析构时自动 unlock(顺序无关)
}
int main() {
Account A(1000), B(1000), C(1000);
// 启动多个并发转账线程
std::vector<std::thread> threads;
// A ↔ B 转账
threads.emplace_back([&]() {
for (int i = 0; i < 500; ++i) {
transfer(A, B, 1);
transfer(B, A, 1);
}
});
// B ↔ C 转账
threads.emplace_back([&]() {
for (int i = 0; i < 500; ++i) {
transfer(B, C, 1);
transfer(C, B, 1);
}
});
// A ↔ C 转账
threads.emplace_back([&]() {
for (int i = 0; i < 500; ++i) {
transfer(A, C, 1);
transfer(C, A, 1);
}
});
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
// 验证总金额守恒(应为 3000)
int total = A.balance + B.balance + C.balance;
std::cout << "A: " << A.balance << "\n";
std::cout << "B: " << B.balance << "\n";
std::cout << "C: " << C.balance << "\n";
std::cout << "Total: " << total << " (should be 3000)\n";
return 0;
}
2.超时机制
cpp
std::timed_mutex mtx;
if (mtx.try_lock_for(std::chrono::seconds(1))) {
// 成功获取锁
mtx.unlock();
} else {
// 超时,放弃或重试
}
3.按层级加锁
把应用程序分层,并且明确每个互斥位于哪个层级。若某线程已对低层级互斥加锁,则不准它再对高层级互斥加锁。具体做法是将层级的编号赋予对应层级应用程序上的互斥,并记录各线程分别锁定了哪些互斥:
cpp
#include <iostream>
#include <thread>
#include <mutex>
//层级锁
class hierarchical_mutex {
public:
explicit hierarchical_mutex(unsigned long value) :_hierarchy_value(value),
_previous_hierarchy_value(0) {}
hierarchical_mutex(const hierarchical_mutex&) = delete;
hierarchical_mutex& operator=(const hierarchical_mutex&) = delete;
void lock() {
check_for_hierarchy_violation();
_internal_mutex.lock();
update_hierarchy_value();
}
void unlock() {
if (_this_thread_hierarchy_value != _hierarchy_value) {
throw std::logic_error("mutex hierarchy violated");
}
_this_thread_hierarchy_value = _previous_hierarchy_value;
_internal_mutex.unlock();
}
bool try_lock() {
check_for_hierarchy_violation();
if (!_internal_mutex.try_lock()) {
return false;
}
update_hierarchy_value();
return true;
}
private:
std::mutex _internal_mutex;
//当前层级值
unsigned long const _hierarchy_value;
//上一次层级值
unsigned long _previous_hierarchy_value;
//本线程记录的层级值
static thread_local unsigned long _this_thread_hierarchy_value;
void check_for_hierarchy_violation() {
if (_this_thread_hierarchy_value <= _hierarchy_value) {
throw std::logic_error("mutex hierarchy violated");
}
}
void update_hierarchy_value() {
_previous_hierarchy_value = _this_thread_hierarchy_value;
_this_thread_hierarchy_value = _hierarchy_value;
}
};
thread_local unsigned long hierarchical_mutex::_this_thread_hierarchy_value(ULONG_MAX);
void test_hierarchy_lock() {
hierarchical_mutex hmtx1(1000);
hierarchical_mutex hmtx2(500);
std::thread t1([&hmtx1, &hmtx2]() {
hmtx1.lock();
hmtx2.lock();
hmtx2.unlock();
hmtx1.unlock();
});
std::thread t2([&hmtx1, &hmtx2]() {
hmtx2.lock();
hmtx1.lock();
hmtx1.unlock();
hmtx2.unlock();
});
t1.join();
t2.join();
}
层级锁能保证我们每个线程加锁时,一定是先加权重高的锁。
并且释放时也保证了顺序。
主要原理就是将当前锁的权重保存在线程变量中,这样该线程再次加锁时判断线程变量的权重和锁的权重是否大于,如果满足条件则继续加锁
4.无锁编程
- 使用原子操作(std::atomic)、CAS 等
- 高性能但复杂,适用于特定场景