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

问题描述 投票:0回答:3

我有这门课(简化):

// 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);
}

如果我调用 process,我会得到一个异常:

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;
     }
}

但是这感觉很脆弱......有正确的方法吗?

c++ mutex recursive-mutex
3个回答
22
投票

首先,

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

要解决您的问题,您可以使用

std::recursive_mutex
,它可以从同一线程多次锁定/解锁。

来自 cppreference:

调用线程在成功调用

recursive_mutex
lock
时开始一段时间内拥有
try_lock
。在此期间,线程可能会额外调用
lock
try_lock
。当线程进行匹配数量的解锁调用时,所有权期限结束。

当一个线程拥有

recursive_mutex
时,如果所有其他线程尝试声明
lock
的所有权,则所有其他线程都将阻塞(对于调用
try_lock
)或收到错误的返回值(对于
recursive_mutex
)。


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


4
投票

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

所以:

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();
}

0
投票

解决方案是使用

std::recursive_mutex
代替
std::mutex

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

  1. 仅仅因为

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

  2. 这绝不是“代码味”!当系统变得复杂时,互斥锁的递归锁有时是不可避免的。如果这是“代码味道”或者可以通过更好的设计来解决,那么

    std::recursive_mutex
    将是一个笑话,应该从 C++ 标准库中转储出来。但它仍然存在,为什么?

© www.soinside.com 2019 - 2024. All rights reserved.