我有这个课程(简化):
// 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;
}
}
然而,这感觉很脆弱......有没有正确的方法来做到这一点?
答案 0 :(得分:12)
首先,volatile
变量不是线程安全的。您必须使用std::atomic<T>
来拥有线程安全的变量。 volatile
与线程安全无关。
要解决您的问题,您可以使用std::recursive_mutex
,可以在同一个帖子中多次锁定/解锁。
来自cppreference:
调用线程拥有
recursive_mutex
一段时间,该时间段在成功调用lock
或try_lock
时开始。在此期间,该主题可能会对lock
或try_lock
进行其他调用。当线程发出匹配的解锁次数时,所有权期限结束。当线程拥有
recursive_mutex
时,如果所有其他线程都试图声明所有权,则所有其他线程将阻止(对于lock
的调用)或接收错误的返回值(对于try_lock
)recursive_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
。
其他答案已经给出了正确的解决方案,但是我想指出另外两件事:
仅因为volatile
不是为线程安全而设计的,但这并不意味着它与线程安全无关。在原始帖子中,volatile bool alreadyLocked;
是正确且足够的。
这绝不是“代码异味”!当系统变得复杂时,有时将不可避免地要互斥锁。如果这是“代码异味”或可以通过更好的设计解决,则std::recursive_mutex
会是一个笑话,应将其转出C ++标准库。但是它仍然在那里,为什么?