// Thread-safe Queue
template<class T>
class BlockingQueue {
public:
BlockingQueue() {
isempty.lock();
}
void push(T obj) {
std::lock_guard<std::mutex> lk(mtx);
data.push(obj);
isempty.unlock(); // Queue is no longer empty
}
T pop() {
isempty.lock(); // Acquired lock means I queue is not empty -- or wait until
// Calling isempty first so that there is no deadlock if pop is blocked on this
// and meanwhile push is called my another thread
mtx.lock();
auto x = data.front();
data.pop();
if(!empty())
{
mtx.unlock();
isempty.unlock(); // Queue is still not empty
}
else {
mtx.unlock(); // Queue is empty don't unlock "isempty"
}
return x;
}
private:
bool empty() {
return data.empty();
}
size_t size() {
return data.size();
}
std::queue<T> data;
std::mutex mtx; // Mutex to stop data-race
std::mutex isempty; // Mutex to block on empty Queue
};
我最近开始学习并发性,并尝试通过在 C++ 中实现阻塞队列来创建一个生产者-消费者示例。我还没有探索信号量或读写锁,并且仅关注使用互斥体进行同步。有人可以检查我的实施并帮助我理解为什么这是错误的。
std::mutex::unlock
:
互斥体必须由当前执行线程锁定,否则行为未定义。
这对你有两个我能看到的方式(可能还有一些我看不到):
push
对于isempty.unlock();
没有解锁时防止通话的保护,也无法合理实现。假设您检查互斥体是否已锁定,却发现在检查后另一个线程插入并锁定或解锁它。必须用另一个互斥体来保护一个互斥体是您走上疯狂之路的明确标志。
一些互斥体实现允许在一个线程中锁定并在另一个线程中解锁,但 C++ 的
std::mutex
不允许。 POSIX 的 pthread
互斥体则不然。 Win32 互斥体,或者至少我所了解和使用的互斥体,没有。
C++ 和 POSIX 中会发生的是未定义行为。也许它会按您想要的方式工作,但您需要测试程序的输出或通过工具链和目标的实现说明来证明该行为将是您想要的。您必须定期重新证明这一点,因为实施细节可能随时发生变化。
就我个人而言,大约 20 年前,我在嵌入式系统上的串行驱动程序中遇到了一种机制。它按设计工作,但需要调试和证明。我花了几个小时挖掘数千行代码,寻找所有解锁,以找到配对的解锁。不要成为那个人。如果有更有针对性的解决方案(如信号量或条件变量)可用,请使用它们并保留互斥体以保护短关键部分。