线程安全堆栈C++中可能存在死锁



在《操作中的并发》一书中,有一个线程安全堆栈的实现,其中在输入pop((和empty((函数时获取/锁定互斥,如下所示:

class threadsafe_stack {
private:
std::stack<T> data;
mutable std::mutex m;
public:
//...
void pop(T& value) {
std::lock_guard<std::mutex> lock(m);
if(data.empty()) throw empty_stack();
value = std::move(data.top());
data.pop();
}
bool empty() const {
std::lock_guard<std::mutex> lock(m);
return data.empty();
}
};

我的问题是,当一个在输入pop((时获得锁的线程正在调用empty((时,这个代码如何不陷入死锁?empty也受到互斥锁的保护?如果lock((是由已经拥有互斥锁的线程调用的,那不是未定义的行为吗?

当一个在输入pop((时获得锁的线程正在调用empty((时,这个代码如何不陷入死锁?

因为您不是在调用threadsafe_stackempty成员函数,而是在调用类std::stack<T>的empty((。如果代码是:

void pop(T& value) 
{
std::lock_guard<std::mutex> lock(m);
if(empty()) // instead of data.empty()
throw empty_stack();
value = std::move(data.top());
data.pop();
}

然后,它将是未定义的行为:

如果锁是由已经拥有互斥的线程调用的,则行为是未定义的:例如,程序可能会死锁。鼓励可以检测无效用法的实现抛出std::system_error,错误条件为resource_deadlock_would_ecurse,而不是死锁。

了解递归和共享互斥。

不能100%确定你的意思,我猜你的意思是在同一个线程中顺序调用popempty?类似于

while(!x.empty()) x.pop();

std::lock_guard遵循RAII。这意味着构造函数

std::lock_guard<std::mutex> lock(m);

将获取/锁定互斥,析构函数(当lock超出作用域时(将再次释放/解锁互斥。所以它在下一次函数调用时被解锁。

pop内部,只调用data.empty(),它不受互斥锁的保护。在pop内部调用this->empty()确实会导致未定义的行为。

如果pop调用this->empty,那就对了。通过std::lock_guard锁定同一个互斥体两次是未定义的行为,除非锁定的互斥体是递归的。

从构造函数(示例代码中使用的构造函数(上的cppreference:

有效地调用m.lock((。如果m不是递归互斥,并且当前线程已经拥有m.,则行为未定义

为了完整起见,有第二个构造函数:

lock_guard( mutex_type& m, std::adopt_lock_t t );

哪个

获取互斥锁m的所有权而不尝试锁定它。如果当前线程不拥有m,则行为未定义。

但是,pop调用data.empty,这是私有成员的方法,而不是threadsafe_stack的成员函数empty。代码中没有问题。

最新更新