如何在同一个线程上使用同一个互斥锁锁定两次?
How can I lock twice with the same mutex on the same thread?
我有这个class(简体):
// 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;
}
}
但是这感觉很脆弱...有正确的方法吗?
首先,volatile
变量 不是线程安全的 。您必须使用 std::atomic<T>
才能拥有线程安全变量。 volatile
与线程安全无关。
要解决您的问题,您可以使用 std::recursive_mutex
,它可以在同一个线程中 locked/unlocked 多次。
来自 cppreference:
A calling thread owns a recursive_mutex
for a period of time that starts when it successfully calls either lock
or try_lock
. During this period, the thread may make additional calls to lock
or try_lock
. The period of ownership ends when the thread makes a matching number of calls to unlock.
When a thread owns a recursive_mutex
, all other threads will block (for calls to lock
) or receive a false return value (for try_lock
) if they attempt to claim ownership of the recursive_mutex
.
此外,请考虑重构您的代码,以便不需要两次锁定互斥量。改进您的设计可能可以避免此问题。
有一个编码技巧可以解决这个设计问题;它被称为递归互斥体。但你真的应该解决设计问题,而不是试图解决它。将代码分为两层:class 中的所有工作都应由不锁定任何内容的私有成员函数完成;外部接口应该通过public成员函数实现,并且他们锁住互斥量
所以:
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();
}
解决办法是用std::recursive_mutex
代替std::mutex
。
其他答案已经给出了正确的解决方案,但我想指出另外两点:
仅仅因为volatile
不是为线程安全设计的,并不意味着它与线程安全无关。原文中post,volatile bool alreadyLocked;
是正确的,足够了。
这绝不是“代码味”!当系统变得复杂时,递归锁定互斥量有时是不可避免的。如果这是“代码味道”或者可以通过更好的设计来解决,那么 std::recursive_mutex
就是一个笑话,应该从 C++ 标准库中删除。但是它仍然存在,为什么?
我有这个class(简体):
// 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;
}
}
但是这感觉很脆弱...有正确的方法吗?
首先,volatile
变量 不是线程安全的 。您必须使用 std::atomic<T>
才能拥有线程安全变量。 volatile
与线程安全无关。
要解决您的问题,您可以使用 std::recursive_mutex
,它可以在同一个线程中 locked/unlocked 多次。
来自 cppreference:
A calling thread owns a
recursive_mutex
for a period of time that starts when it successfully calls eitherlock
ortry_lock
. During this period, the thread may make additional calls tolock
ortry_lock
. The period of ownership ends when the thread makes a matching number of calls to unlock.When a thread owns a
recursive_mutex
, all other threads will block (for calls tolock
) or receive a false return value (fortry_lock
) if they attempt to claim ownership of therecursive_mutex
.
此外,请考虑重构您的代码,以便不需要两次锁定互斥量。改进您的设计可能可以避免此问题。
有一个编码技巧可以解决这个设计问题;它被称为递归互斥体。但你真的应该解决设计问题,而不是试图解决它。将代码分为两层:class 中的所有工作都应由不锁定任何内容的私有成员函数完成;外部接口应该通过public成员函数实现,并且他们锁住互斥量
所以:
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();
}
解决办法是用std::recursive_mutex
代替std::mutex
。
其他答案已经给出了正确的解决方案,但我想指出另外两点:
仅仅因为
volatile
不是为线程安全设计的,并不意味着它与线程安全无关。原文中post,volatile bool alreadyLocked;
是正确的,足够了。这绝不是“代码味”!当系统变得复杂时,递归锁定互斥量有时是不可避免的。如果这是“代码味道”或者可以通过更好的设计来解决,那么
std::recursive_mutex
就是一个笑话,应该从 C++ 标准库中删除。但是它仍然存在,为什么?