C++-死锁

死锁

当一个操作需要两个及以上的互斥锁,就可能发生死锁。多个线程分别已经获取到其中一个互斥锁,而它们又在互相等待其他线程释放对方的互斥锁,从而导致死锁。注意,当线程相互等待时,也会造成“死锁”,即使两个线程内都没有“锁”。

避免死锁

通常情况下,避免死锁的建议之一是将多个互斥锁总是以相同的顺序锁住。例如有两个互斥锁A和B,我们总是保证先获取互斥锁A再获取互斥锁B。
但在某些情况下,要保证这一点会很困难,例如每个对象内都有一个互斥锁。考虑有一个类包含一个互斥锁成员,一个交换数据的方法swap,现在有两个该类的对象X、Y,当我们调用swap交换X、Y的数据时,swap内部总是先获取第一个参数对象的互斥锁,然后获取第二个参数对象的互斥锁。现在,如果有两个线程都试图调用swap交换X、Y的数据,但是一个线程调用swap(X,Y),另一个调用swap(Y,X),这样就会造成死锁。

还好,C++标准库有提供了一个函数std::lock,它可以一次性锁住多个互斥锁,并且要么在这些互斥锁全部获取成功,要么全部失败,从而避免上述死锁情形发生。

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);
        std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);
        std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);
        swap(lhs.some_detail, rhs.some_detail);
    }
};

&lhs == &rhs检查避免了一个锁被lock两次,那将导致未定义的行为。std::lock一次性获取两个互斥锁,lock_guard默认会在构造函数内锁住互斥锁,std::adopt_lock表明此时互斥锁已经锁住,构造函数将不会再获取这个互斥锁。lock_guard保证了即使发生了异常也能正常释放互斥锁。对于std::lock调用,如果一个锁成功获取后,另一个锁获取失败,将抛出一个异常,并且已经获取成功的锁将释放。

c++17提供了一个可变参数模板std::scoped_lock<>,相当于是std::lock和std::lock_guard的结合,他在构造函数内获取所有锁,析构函数内释放所有锁,并且保证锁要么全部获取或要么全部不获取。

void swap(X& lhs, X& rhs)
{
    if(&lhs==&rhs)
        return;
    //c++17自动类模板参数推导
    //等价于std::scoped_lock<std::mutex,std::mutex> guard(lhs.m,rhs.m);
    std::scoped_lock guard(lhs.m,rhs.m);
    swap(lhs.some_detail,rhs.some_detail);
}

避免死锁的几个原则

尽管std::lock能保证在同时获取多个锁的情况下不发生死锁,但是如果多个锁必须分别获取,那就只能依靠一些原则来避免死锁。这些原则的思想归成一句话就是:不要等待一个“可能等待当前线程的线程”。

  • 避免嵌套锁
    即如果你已经有了一个锁,则不要再请求其他锁(即嵌套),如果要获取多个锁,请使用std::lock
  • 拥有锁的情况下避免执行用户代码
    因为用户可能请求锁,如果用户请求的锁就是当前拥有的锁,那么就会造成死锁
  • 将多个锁总是按照固定顺序获取
  • 使用带层级的锁
    即给每个锁一个"层级",如500,700,1000,当已经拥有层级高的锁,就不能获取同级或者层级更低的锁,否则抛出一个异常。因为层级锁本质上也是规定了获取锁的顺序,因此能避免死锁。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
C++死锁是指在多线程编程中,两个或多个线程互相等待对方释放资源而无法继续执行的情况。这种情况下,程序陷入无限等待的状态,无法正常运行。 死锁通常发生在多个线程同时访问共享资源时,每个线程都持有一个资源,并且试图获取其他线程持有的资源。当所有线程都无法获取到所需的资源时,就发生死锁。 为了避免死锁的发生,可以采取以下几种方法: 1. 避免使用多个:尽量减少使用多个来控制资源的访问,可以使用更细粒度的或者使用无数据结构来避免死锁的发生。 2. 使用加顺序:对于多个资源,确保所有线程以相同的顺序获取,这样可以避免死锁的发生。 3. 使用超时机制:在获取的过程中设置超时机制,如果超过一定时间还未获取到所需的资源,就主动释放已经获取的资源,避免陷入死锁死锁检测工具是用来检测程序中是否存在潜在的死锁问题的工具。常见的死锁检测工具有: 1. Valgrind:一个开源的内存调试和性能分析工具,其中包含了Helgrind工具,可以检测多线程程序中的死锁问题。 2. ThreadSanitizer:一个用于检测并发错误的工具,可以检测死锁、数据竞争等问题。 3. Visual Studio的并发视图器:在Visual Studio中提供了一个并发视图器,可以帮助开发者分析多线程程序中的死锁问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mrbone11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值