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),禁止!

总结

  1. 场景一:互斥锁的嵌套获取导致死锁。解决方法是使用 std::lock 同时锁定多个互斥量。
  2. 场景二:在持有锁时调用用户代码可能导致死锁。解决方法是在持有锁时避免调用可能获取其他锁的代码。
  3. 场景三:固定顺序获取锁不一致导致死锁。解决方法是确保所有线程以相同的顺序获取锁。
  4. 层次锁:

    优点:(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();         // 更新线程层级值
}

执行顺序很重要

  1. 先检查层级(如果失败,不锁定)

  2. 再实际锁定

  3. 最后更新层级值

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? ✗
}
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐