调用带锁的成员函数?
Call a member function with lock?
我设计了一个如下的队列,一个问题是如果成员函数里面有锁,我想在另一个也有锁的成员函数中调用它。这样会不会造成可重入锁的问题?
class MyQueue {
public:
bool Empty() {
std::lock_guard<std::mutex> lock(mutex_);
return queue_.size() == 0;
}
bool Dequeue(std::string& data) {
std::lock_guard<std::mutex> lock(mutex_);
if (Empty()) return false; // reentrant locks error!!!
data = queue_.front();
queue_.pop();
return true;
}
}
我的解决方案是不调用成员函数,避免它的最佳做法是什么?
bool Dequeue(std::string& data) {
std::lock_guard<std::mutex> lock(mutex_);
if (queue_.size() == 0) return false; // don't call member function!!!
data = queue_.front();
queue_.pop();
return true;
}
如果一个 public 成员函数需要调用另一个 public 成员函数,这两个成员函数锁定同一个互斥量,那么最好将它们分开,以便它们调用一个私有成员函数,而不是'锁。私有成员函数假定已持有锁。这样您就可以避免必须使用递归互斥锁,这通常是设计缺陷的标志。
所以我会创建一个不锁定的私有 IsEmpty()
函数,并从 Dequeue()
和任何其他 public 成员函数调用它:
class MyQueue {
public:
bool Dequeue(std::string& data) {
std::lock_guard<std::mutex> lock(mutex_);
if (IsEmpty()) return false;
data = queue_.front();
queue_.pop();
return true;
}
private:
bool IsEmpty() {
return queue_.empty();
}
...
};
这也将允许您以通常的方式使用条件变量。 std::condition_variable
只释放一级锁,但是如果你在递归互斥锁上使用锁,你可能仍然持有锁并因此死锁。
你需要问问自己在多线程程序中你是否真的需要一个像Empty()
这样的public函数。如果你有不同的线程在任何时候向你的队列中添加和弹出元素,那么这样的功能可能不值得。但是,如果你真的想要一个 public 成员函数 Empty()
,它必须锁定互斥锁,然后调用私有 IsEmpty()
函数。
请注意,您可以在 std::queue
等标准容器上调用成员函数 empty()
而不是 queue_.size() == 0
,在这种情况下,您可能可以取消 [=11] =] 完全。
在这种特殊情况下,您最后的解决方案是好的 - 调用容器函数。
介绍像
这样的东西
private:
bool IsEmpty() {
return queue_.empty();
}
只是成员函数的不必要包装,只会膨胀代码。
但是如果您的 IsEmpty()
函数应该做更多的事情,那么只有这样才有意义。但是从你的代码示例来看,它看起来不像这样。
此外 - 关于最佳 C++ 编码实践的另一项建议 - 如果您想测试容器是否为空,请使用 container.empty()
而不是 container.size() == 0
。
我设计了一个如下的队列,一个问题是如果成员函数里面有锁,我想在另一个也有锁的成员函数中调用它。这样会不会造成可重入锁的问题?
class MyQueue {
public:
bool Empty() {
std::lock_guard<std::mutex> lock(mutex_);
return queue_.size() == 0;
}
bool Dequeue(std::string& data) {
std::lock_guard<std::mutex> lock(mutex_);
if (Empty()) return false; // reentrant locks error!!!
data = queue_.front();
queue_.pop();
return true;
}
}
我的解决方案是不调用成员函数,避免它的最佳做法是什么?
bool Dequeue(std::string& data) {
std::lock_guard<std::mutex> lock(mutex_);
if (queue_.size() == 0) return false; // don't call member function!!!
data = queue_.front();
queue_.pop();
return true;
}
如果一个 public 成员函数需要调用另一个 public 成员函数,这两个成员函数锁定同一个互斥量,那么最好将它们分开,以便它们调用一个私有成员函数,而不是'锁。私有成员函数假定已持有锁。这样您就可以避免必须使用递归互斥锁,这通常是设计缺陷的标志。
所以我会创建一个不锁定的私有 IsEmpty()
函数,并从 Dequeue()
和任何其他 public 成员函数调用它:
class MyQueue {
public:
bool Dequeue(std::string& data) {
std::lock_guard<std::mutex> lock(mutex_);
if (IsEmpty()) return false;
data = queue_.front();
queue_.pop();
return true;
}
private:
bool IsEmpty() {
return queue_.empty();
}
...
};
这也将允许您以通常的方式使用条件变量。 std::condition_variable
只释放一级锁,但是如果你在递归互斥锁上使用锁,你可能仍然持有锁并因此死锁。
你需要问问自己在多线程程序中你是否真的需要一个像Empty()
这样的public函数。如果你有不同的线程在任何时候向你的队列中添加和弹出元素,那么这样的功能可能不值得。但是,如果你真的想要一个 public 成员函数 Empty()
,它必须锁定互斥锁,然后调用私有 IsEmpty()
函数。
请注意,您可以在 std::queue
等标准容器上调用成员函数 empty()
而不是 queue_.size() == 0
,在这种情况下,您可能可以取消 [=11] =] 完全。
在这种特殊情况下,您最后的解决方案是好的 - 调用容器函数。 介绍像
这样的东西private:
bool IsEmpty() {
return queue_.empty();
}
只是成员函数的不必要包装,只会膨胀代码。
但是如果您的 IsEmpty()
函数应该做更多的事情,那么只有这样才有意义。但是从你的代码示例来看,它看起来不像这样。
此外 - 关于最佳 C++ 编码实践的另一项建议 - 如果您想测试容器是否为空,请使用 container.empty()
而不是 container.size() == 0
。