C++线程--互斥锁的风险

前言

互斥锁的使用似乎非常简单。
只要保证代码中的关键部分,只能在任何时间点由单个线程访问就行了。一个互斥锁变量mt通过调用m.lock()和m.unlock()就保证了这种排他性。但是,魔鬼在于细节。。。


死锁

死锁的不同名字很可怕。有人称之为致命拥抱?或者死亡之吻。
但是等等,什么是死锁?
死锁是一种状态,其中至少有两个线程被阻塞,因为每个线程都在等待释放其他线程工作的某些资源,然后才释放它自己的资源。
死锁的结果是完全停顿。线程和整个程序通常被永远阻塞。死锁很容易产生,好奇吗?

// Exceptions and unknown code
std::mutex m;
m.lock();
sharedVariable= getVar();
m.unlock();

如果函数getVar()中的未知代码抛出异常,将不调用m.unlock()。
这样导致每一次请求互斥锁m的尝试都会失败,程序会永远阻塞在这里。
但这并不是这段代码唯一的问题。它调用一些未知的函数getVar(),而m.lock()则是活动的。如果函数getVar()也试图获得相同的锁,调用m.lock,你猜会发生什么?当然,你知道,死锁。


你想要一个更直观的例子吗?
按不同顺序锁定互斥锁

这里写图片描述

线程1和线程2需要访问两个资源才能完成它们的工作。不幸的是,他们要求由两个互斥体以不同的顺序保护的资源。
在这种情况下,线程执行将以这样的方式交织,即线程1得到互斥锁1,然后线程2得到互斥锁2,并且卡在这里。每个线程都希望得到另一个互斥锁。为此,线程必须等待资源的释放。


代码来描述上面的图片:

// deadlock.cpp

#include <iostream>
#include <chrono>
#include <mutex>
#include <thread>

struct CriticalData 
{
    std::mutex mut;
};

void deadLock(CriticalData& a, CriticalData& b) 
{
    a.mut.lock();
    std::cout << "get the first mutex" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
    b.mut.lock();
    std::cout << "get the second mutex" << std::endl;
    // do something with a and b
    a.mut.unlock();
    b.mut.unlock();
}

int main()
{
    CriticalData c1;
    CriticalData c2;

    std::thread t1([&] 
    {
        deadLock(c1, c2); 
    });

    std::thread t2([&] 
    {
        deadLock(c2, c1); 
    });

    t1.join();
    t2.join();
}

线程t1和线程t2调用函数deadLock。
为了执行deadLock,两个函数都需要临界数据c1和c2。因为对象c1和c2必须受到共享访问的保护,所以它们有互斥锁(为了这个示例代码简短些,除了互斥锁之外没有任何其他方法或成员)。
函数中间只有一个大约1毫秒的睡眠,死锁了:

这里写图片描述

现在唯一的选择是按下Ctrl +C来杀死进程。


原文地址:

http://www.modernescpp.com/index.php/the-risk-of-mutexes

猜你喜欢

转载自blog.csdn.net/y396397735/article/details/81008447