如何在同一个线程上使用相同的互斥锁锁定两次?

时间:2017-10-16 09:50:55

标签: c++ mutex

我有这个课程(简化):

// thing.h

#include <mutex>

class Thing
{
public:
    void process();
    void inner();

private:
    std::mutex lock;
};

// thing.cpp

#include "Thing.h"

using namespace std;

void Thing::process()
{
    lock_guard<mutex> locking(lock);

    inner();
}

void Thing::inner()
{
    lock_guard<mutex> locking(lock);
}

如果我打电话处理,我会得到一个例外:

Microsoft C++ exception: std::system_error at memory location 0x006FF16C.

锁定同一线程中的同一个锁会导致此异常。除了例外,我怎么能这样做?我想添加一个标志:

volatile bool alreadyLocked;

将内部改为:

void Thing::inner()
{
     if (!alreadyLocked)
     {
         lock_guard<mutex> locking(lock);
         alreadyLocked = true;
         ...something magic happens here...
         alreadyLocked = false;
     }
}

然而,这感觉很脆弱......有没有正确的方法来做到这一点?

3 个答案:

答案 0 :(得分:12)

首先,volatile变量不是线程安全的。您必须使用std::atomic<T>来拥有线程安全的变量。 volatile与线程安全无关。

要解决您的问题,您可以使用std::recursive_mutex,可以在同一个帖子中多次锁定/解锁。

来自cppreference:

  

调用线程拥有recursive_mutex一段时间,该时间段在成功调用locktry_lock时开始。在此期间,该主题可能会对locktry_lock进行其他调用。当线程发出匹配的解锁次数时,所有权期限结束。

     

当线程拥有recursive_mutex时,如果所有其他线程都试图声明所有权,则所有其他线程将阻止(对于lock的调用)或接收错误的返回值(对于try_lockrecursive_mutex

此外,请考虑重构代码,以便不需要锁定互斥锁两次。改进您的设计可能可以避免这个问题。

答案 1 :(得分:2)

有一个编码黑客来解决这个设计问题;它被称为递归互斥体。但你真的应该解决设计问题,而不是试图解决它。将代码分成两层:类中的所有工作都应该由不锁定任何东西的私有成员函数完成;外部接口应该通过公共成员函数实现,他们锁定互斥锁。

所以:

class Thing {
public:
    void process();
    void inner();
private:
    void do_process();
    void do_inner();
    std::mutex mtx;
};

void Thing::process() {
    std::lock_guard<std::mutex> lock(mtx);
    do_process();
}

void Thing::inner() {
    std::lock_guard<std::mutex> lock(mtx);
    do_inner();
}

void Thing::do_process() {
    do_inner();
}

答案 2 :(得分:0)

解决方案是使用std::recursive_mutex而不是std::mutex

其他答案已经给出了正确的解决方案,但是我想指出另外两件事:

  1. 仅因为volatile不是为线程安全而设计的,但这并不意味着它与线程安全无关。在原始帖子中,volatile bool alreadyLocked;是正确且足够的。

  2. 这绝不是“代码异味”!当系统变得复杂时,有时将不可避免地要互斥锁。如果这是“代码异味”或可以通过更好的设计解决,则std::recursive_mutex会是一个笑话,应将其转出C ++标准库。但是它仍然在那里,为什么?