调用带锁的成员函数?

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