C++并发编程(4)——死锁问题
class some_big_object;
void swap(some_big_object& lhs,some_big_object& rhs);
class X
{
private:
some_big_object some_detail;
std::mutex m;
public:
X(some_big_object const& sd):some_detail(sd){}
friend void swap(X& lhs, X& rhs)
{
if(&lhs==&rhs)
return;
std::lock(lhs.m,rhs.m); // 1
std::lock_guard<std::mutex> lock_a(lhs.m,std::adopt_lock);
// 2
std::lock_guard<std::mutex> lock_b(rhs.m,std::adopt_lock);
// 3
swap(lhs.some_detail,rhs.some_detail);
}
};
1. 避免死锁的锁定策略
std::lock(lhs.m,rhs.m); // 一次性锁定两个互斥量,避免死锁
-
使用
std::lock()可以一次性锁定两个或多个互斥量,避免了传统分别锁定可能导致的死锁问题(如线程A锁定lhs.m、线程B锁定rhs.m,然后互相等待)
2. std::lock_guard与std::adopt_lock的配合
std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);
std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);
-
std::adopt_lock参数告诉lock_guard互斥量已经被锁定,只需要负责解锁 -
这样可以保证异常安全性(即使
swap抛出异常,互斥量也能正确解锁)
3. 自交换检查
if(&lhs==&rhs) return;
-
防止自交换时重复锁定同一个互斥量导致的未定义行为
4. 线程安全的成员函数设计
-
展示了如何为包含互斥量的类实现线程安全的操作
-
通过友元函数访问私有成员,同时保证线程安全
对比传统不安全的实现:
// 不安全的实现 - 可能导致死锁
void swap(X& lhs, X& rhs) {
std::lock_guard<std::mutex> lock_a(lhs.m); // 线程A锁定lhs.m
std::lock_guard<std::mutex> lock_b(rhs.m); // 线程B可能锁定rhs.m
swap(lhs.some_detail, rhs.some_detail); // 可能导致死锁
}
C++17及以后的改进:
现代C++可以使用std::scoped_lock更简洁地实现:
void swap(X& lhs, X& rhs) {
if(&lhs == &rhs) return;
std::scoped_lock lock(lhs.m, rhs.m); // 一次性锁定多个互斥量
swap(lhs.some_detail, rhs.some_detail);
}
这个例子是C++并发编程中的经典模式,展示了如何安全地操作多个受互斥量保护的资源。
死锁场景分析
假设有两个线程同时调用swap:
线程1:
swap(x1, x2); // x1和x2是X的两个不同对象
线程2:
swap(x2, x1); // 注意:参数顺序相反!
执行时间线(可能的情况):
时间 线程1 线程2
t0 锁定 x1.m (成功)
t1 锁定 x2.m (成功)
t2 尝试锁定 x2.m (等待) <-- 死锁!
t3 尝试锁定 x1.m (等待) <-- 死锁!
避免死锁的一些实践
1. 避免嵌套锁
- 基本原则:一个线程在持有锁的情况下,尽量避免再去获取其他锁。这是因为如果一个线程持有一个锁并试图获取另一个锁,可能会导致其他线程等待,从而产生死锁。
- 解决方法:如果需要获取多个锁,建议使用
std::lock,它可以同时锁定多个互斥量,避免了死锁的风险。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mutexA;
std::mutex mutexB;
void threadFunction1() {
std::lock_guard<std::mutex> lockA(mutexA);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟工作
std::lock_guard<std::mutex> lockB(mutexB); // 尝试获取锁B
std::cout << "Thread 1 is working with resources A and B." << std::endl;
}
void threadFunction2() {
std::lock_guard<std::mutex> lockB(mutexB);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟工作
std::lock_guard<std::mutex> lockA(mutexA); // 尝试获取锁A
std::cout << "Thread 2 is working with resources A and B." << std::endl;
}
int main() {
std::thread t1(threadFunction1);
std::thread t2(threadFunction2);
t1.join();
t2.join();
return 0;
}
解决办法:
void threadFunction1() {
std::lock(mutexA, mutexB); // 同时锁定
std::lock_guard<std::mutex> lockA(mutexA, std::adopt_lock);
std::lock_guard<std::mutex> lockB(mutexB, std::adopt_lock);
std::cout << "Thread 1 is working with resources A and B." << std::endl;
}
void threadFunction2() {
std::lock(mutexA, mutexB); // 同时锁定
std::lock_guard<std::mutex> lockA(mutexA, std::adopt_lock);
std::lock_guard<std::mutex> lockB(mutexB, std::adopt_lock);
std::cout << "Thread 2 is working with resources A and B." << std::endl;
}
2.避免在持有锁时调用用户提供的代码
-
原因:用户提供的代码行为是不可预测的,它可能会尝试获取锁,从而违反上述的“避免嵌套锁”原则,导致死锁。
-
建议:在设计通用代码(如容器)时,应尽量避免在持有锁的情况下调用用户提供的代码。如果必须这样做,需要确保用户代码不会获取锁,或者采取其他措施来避免死锁。
死锁场景:一个线程在持有锁时调用用户提供的函数,该函数也试图获取锁。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mutexA;
void userProvidedFunction() {
std::lock_guard<std::mutex> lock(mutexA); // 尝试获取锁
std::cout << "User provided function is running." << std::endl;
}
void threadFunction() {
std::lock_guard<std::mutex> lock(mutexA);
userProvidedFunction(); // 在持有锁时调用用户代码
}
int main() {
std::thread t1(threadFunction);
std::thread t2(threadFunction);
t1.join();
t2.join();
return 0;
}
解决办法:
void threadFunction() {
std::lock_guard<std::mutex> lock(mutexA);
std::cout << "Thread is working with resource A." << std::endl;
// 不调用 userProvidedFunction(),避免死锁
}
3.使用固定顺序获取锁
-
适用场景:当必须获取多个锁,且不能使用
std::lock时,应确保所有线程都以相同的顺序获取这些锁。 -
目的:固定顺序获取锁可以避免死锁,因为这样可以防止循环等待条件的发生。例如,如果所有线程都先获取锁A再获取锁B,那么就不会出现线程1持有A等待B,同时线程2持有B等待A的情况。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mutexA;
std::mutex mutexB;
void threadFunction1() {
std::lock_guard<std::mutex> lockA(mutexA);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟工作
std::lock_guard<std::mutex> lockB(mutexB); // 尝试获取锁B
std::cout << "Thread 1 is working with resources A and B." << std::endl;
}
void threadFunction2() {
std::lock_guard<std::mutex> lockB(mutexB);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟工作
std::lock_guard<std::mutex> lockA(mutexA); // 尝试获取锁A
std::cout << "Thread 2 is working with resources A and B." << std::endl;
}
int main() {
std::thread t1(threadFunction1);
std::thread t2(threadFunction2);
t1.join();
t2.join();
return 0;
}
解决办法:
void threadFunction1() {
std::lock_guard<std::mutex> lockA(mutexA);
std::lock_guard<std::mutex> lockB(mutexB); // 确保顺序一致
std::cout << "Thread 1 is working with resources A and B." << std::endl;
}
void threadFunction2() {
std::lock_guard<std::mutex> lockA(mutexA); // 确保顺序一致
std::lock_guard<std::mutex> lockB(mutexB);
std::cout << "Thread 2 is working with resources A and B." << std::endl;
}
4.使用锁的层次结构
hierarchical_mutex high_level_mutex(10000); // 1
hierarchical_mutex low_level_mutex(5000); // 2
hierarchical_mutex other_mutex(6000); // 3
int do_low_level_stuff();
int low_level_func()
{
std::lock_guard<hierarchical_mutex> lk(low_level_mutex); // 4
return do_low_level_stuff();
}
void high_level_stuff(int some_param);
void high_level_func()
{
std::lock_guard<hierarchical_mutex> lk(high_level_mutex); // 6
high_level_stuff(low_level_func()); // 5
}
void thread_a() // 7
{
high_level_func();
}
void do_other_stuff();
void other_stuff()
{
high_level_func(); // 10
do_other_stuff();
}
void thread_b() // 8
{
std::lock_guard<hierarchical_mutex> lk(other_mutex); // 9
other_stuff();
}
解决原理:
线程A的执行路径(正确顺序):
thread_a()
↓
high_level_func()
↓ 1. 锁定 high_level_mutex(10000)
当前线程层级 = 10000
↓
high_level_stuff(low_level_func())
↓
low_level_func()
↓ 2. 尝试锁定 low_level_mutex(5000)
检查:5000 < 10000 ✓(允许)
当前线程层级 = 5000
↓
do_low_level_stuff()
↓ 3. 解锁 low_level_mutex
恢复线程层级 = 10000
↓ 4. 解锁 high_level_mutex
线程层级 = 未定义/初始值
线程B的执行路径(会导致异常):
thread_b()
↓ 1. 锁定 other_mutex(6000)
当前线程层级 = 6000
↓
other_stuff()
↓
high_level_func()
↓ 2. 尝试锁定 high_level_mutex(10000)
检查:10000 > 6000 ✗(违反规则!)
抛出异常或终止程序
为什么能避免死锁?
1. 强制线性顺序
text
高层级锁 (10000)
↓
中层级锁 (6000)
↓
低层级锁 (5000)
-
所有线程必须从上到下获取锁
-
不可能出现A持有低层级锁,B持有高层级锁的情况
2. 运行时检查
// hierarchical_mutex的lock()方法大概实现
void lock() {
check_for_hierarchy_violation(); // 检查是否违反层级
internal_mutex.lock(); // 实际锁定
update_hierarchy_value(); // 更新线程当前层级
}
3. 预防交叉锁定
假设有两个锁:
-
锁A:层级100
-
锁B:层级50
传统方式可能死锁:
线程1: lock(A) → 尝试lock(B) 线程2: lock(B) → 尝试lock(A) // 死锁!
层级互斥量强制:
线程1: lock(A) → lock(B) ✓ // A(100)>B(50),允许 线程2: 不能先lock(B)再lock(A) // B(50)<A(100),禁止!
总结
- 场景一:互斥锁的嵌套获取导致死锁。解决方法是使用
std::lock同时锁定多个互斥量。 - 场景二:在持有锁时调用用户代码可能导致死锁。解决方法是在持有锁时避免调用可能获取其他锁的代码。
- 场景三:固定顺序获取锁不一致导致死锁。解决方法是确保所有线程以相同的顺序获取锁。
- 层次锁:
优点:(1)预防死锁:设计时就能避免,不是运行时检测(2)早期发现问题:违反层级立即抛出异常(3)代码清晰:锁的顺序在设计中明确 缺点:(1)灵活性差:不能根据运行时条件改变锁定顺序(2)层级设计复杂:大型系统需要精心设计层级结构(3)可能过度约束:有时需要违反层级但安全的场景无法处理
补充:简单层级锁的实现
class hierarchical_mutex {
std::mutex internal_mutex;
unsigned long const hierarchy_value;
unsigned long previous_hierarchy_value;
static thread_local unsigned long this_thread_hierarchy_value; // 1
void check_for_hierarchy_violation() {
if (this_thread_hierarchy_value <= hierarchy_value) { // 2
throw std::logic_error("mutex hierarchy violated");
}
}
void update_hierarchy_value() {
previous_hierarchy_value = this_thread_hierarchy_value; // 3
this_thread_hierarchy_value = hierarchy_value;
}
public:
explicit hierarchical_mutex(unsigned long value)
: hierarchy_value(value), previous_hierarchy_value(0) {}
void lock() {
check_for_hierarchy_violation();
internal_mutex.lock(); // 4
update_hierarchy_value(); // 5
}
void unlock() {
if (this_thread_hierarchy_value != hierarchy_value)
throw std::logic_error("mutex hierarchy violated"); // 9
this_thread_hierarchy_value = previous_hierarchy_value; // 6
internal_mutex.unlock();
}
bool try_lock() {
check_for_hierarchy_violation();
if (!internal_mutex.try_lock()) // 7
return false;
update_hierarchy_value();
return true;
}
};
// 初始化静态成员变量
thread_local unsigned long hierarchical_mutex::this_thread_hierarchy_value(ULONG_MAX); // 8
核心设计要素
1. 线程局部存储(关键)
static thread_local unsigned long this_thread_hierarchy_value;
-
thread_local:每个线程有自己独立的副本 -
存储当前线程已持有锁的最高层级值
-
初始化为
ULONG_MAX(最大值)
2. 层级规则验证
void check_for_hierarchy_violation()
{
if(this_thread_hierarchy_value <= hierarchy_value) // 2
{
throw std::logic_error("mutex hierarchy violated");
}
}
规则:新锁的层级值必须严格小于当前线程的层级值
-
初始时:
ULONG_MAX > 任何值✓ -
持有高层级锁后:只能获取更低层级的锁
3. 层级值管理
void update_hierarchy_value()
{
previous_hierarchy_value = this_thread_hierarchy_value; // 3
this_thread_hierarchy_value = hierarchy_value;
}
-
previous_hierarchy_value:保存旧值,用于解锁时恢复 -
更新为当前锁的(更低)层级值
方法详细分析
lock() 方法
void lock()
{
check_for_hierarchy_violation(); // 检查是否违反层级
internal_mutex.lock(); // 实际锁定底层mutex
update_hierarchy_value(); // 更新线程层级值
}
执行顺序很重要:
-
先检查层级(如果失败,不锁定)
-
再实际锁定
-
最后更新层级值
unlock() 方法
void unlock()
{
if(this_thread_hierarchy_value != hierarchy_value) // 9
throw std::logic_error("mutex hierarchy violated");
this_thread_hierarchy_value = previous_hierarchy_value; // 6
internal_mutex.unlock();
}
关键检查:确保解锁的是最近获取的锁
-
防止错误的解锁顺序
-
恢复线程的层级值到之前的状态
try_lock() 方法
bool try_lock()
{
check_for_hierarchy_violation();
if(!internal_mutex.try_lock()) // 7
return false;
update_hierarchy_value();
return true;
}
-
非阻塞版本
-
层级检查失败也会立即返回
初始化
thread_local unsigned long
hierarchical_mutex::this_thread_hierarchy_value(ULONG_MAX); // 8
-
静态成员在类外定义
-
每个线程初始为最大值,表示"未持有任何层级锁"
使用示例重现
// 创建不同层级的锁
hierarchical_mutex level_10000(10000);
hierarchical_mutex level_5000(5000);
hierarchical_mutex level_1000(1000);
// 正确使用:从高到低
void correct_usage() {
std::lock_guard<hierarchical_mutex> l1(level_10000); // 线程层级: ∞→10000
std::lock_guard<hierarchical_mutex> l2(level_5000); // 线程层级: 10000→5000 ✓
std::lock_guard<hierarchical_mutex> l3(level_1000); // 线程层级: 5000→1000 ✓
// 解锁时自动恢复:1000→5000→10000→∞
}
// 错误使用:从低到高
void wrong_usage() {
std::lock_guard<hierarchical_mutex> l1(level_1000); // 线程层级: ∞→1000
// 下一行会抛出异常!
std::lock_guard<hierarchical_mutex> l2(level_5000); // 检查:5000 <= 1000? ✗
}
更多推荐



所有评论(0)