C++ 并发队列中的竞争条件
我目前正试图编写一个并发队列,但我有一些无法向自己解释的错误。我的队列实现基本上由这个站点上的第一个列表给出 该网站说,如果并行地从队列中移除对象,则存在竞争条件,但我不明白为什么会有竞争条件,有人能向我解释一下吗 编辑:这是代码:C++ 并发队列中的竞争条件,c++,concurrency,queue,race-condition,C++,Concurrency,Queue,Race Condition,我目前正试图编写一个并发队列,但我有一些无法向自己解释的错误。我的队列实现基本上由这个站点上的第一个列表给出 该网站说,如果并行地从队列中移除对象,则存在竞争条件,但我不明白为什么会有竞争条件,有人能向我解释一下吗 编辑:这是代码: template<typename Data> class concurrent_queue { private: std::queue<Data> the_queue; mutable boost::mutex the_m
template<typename Data>
class concurrent_queue
{
private:
std::queue<Data> the_queue;
mutable boost::mutex the_mutex;
public:
void push(const Data& data)
{
boost::mutex::scoped_lock lock(the_mutex);
the_queue.push(data);
}
bool empty() const
{
boost::mutex::scoped_lock lock(the_mutex);
return the_queue.empty();
}
Data& front()
{
boost::mutex::scoped_lock lock(the_mutex);
return the_queue.front();
}
Data const& front() const
{
boost::mutex::scoped_lock lock(the_mutex);
return the_queue.front();
}
void pop()
{
boost::mutex::scoped_lock lock(the_mutex);
the_queue.pop();
}
};
模板
类并发队列
{
私人:
std::将_队列排队;
mutable boost::mutex the_mutex;
公众:
无效推送(常量数据和数据)
{
boost::mutex::作用域的_锁锁(_互斥锁);
_queue.push(数据);
}
bool empty()常量
{
boost::mutex::作用域的_锁锁(_互斥锁);
返回_queue.empty();
}
数据与前端()
{
boost::mutex::作用域的_锁锁(_互斥锁);
返回_queue.front();
}
数据常量&front()常量
{
boost::mutex::作用域的_锁锁(_互斥锁);
返回_queue.front();
}
void pop()
{
boost::mutex::作用域的_锁锁(_互斥锁);
_queue.pop();
}
};
如果使用empty并发现队列不为空,则在使用结果之前,另一个线程可能已弹出该项,使其为空
同样,对于front,您可以读取front项,并且在您使用该项时,它可能会被另一个线程弹出。如果在您尝试读取该项时队列为空,该怎么办 想想这个用户代码:
while(!q.empty()) //here you check q is not empty
{
//since q is not empty, you enter inside the loop
//BUT before executing the next statement in this loop body,
//the OS transfers the control to the other thread
//which removes items from q, making it empty!!
//then this thread executes the following statement!
auto item = q.front(); //what would it do (given q is empty?)
}
来自@parkydr和@Nawaz的答案是正确的,但这里还有另一个值得思考的问题 你想达到什么目标 使用线程安全队列的原因有时(我不敢经常说)是错误的。在许多情况下,您希望在队列只是一个实现细节的上下文中锁定队列的“外部” 然而,线程安全队列的一个原因是消费者-生产者情况,在这种情况下,1-N个节点推送数据,而1-M个节点从数据中弹出,而不管它们得到什么。队列中的所有元素都被平等对待,消费者在不知道他们得到了什么的情况下突然出现,并开始处理数据。在这种情况下,您的接口不应公开
T&front()
。好吧,如果您不确定其中是否有项,则永远不应该返回引用(在并行情况下,如果没有外部锁,您永远不可能确定)
我建议使用unique\u ptr
(当然也可以使用shared\u ptr
),并且只公开无种族竞争的函数(为了简洁起见,我省略了const函数)。使用std::unique_ptr
需要C++11,但如果您无法使用C++11,您可以使用boost::shared_ptr
实现相同的功能:
// Returns the first item, or an empty unique_ptr
std::unique_ptr< T > pop( );
// Returns the first item if it exists. Otherwise, waits at most <timeout> for
// a value to be be pushed. Returns an empty unique_ptr if timeout was reached.
std::unique_ptr< T > pop( {implementation-specific-type} timeout );
void push( std::unique_ptr< T >&& ptr );
//返回第一项,或一个空的唯一\u ptr
std::unique_ptrpop();
//返回第一项(如果存在)。否则,最多只能等待
//要推送的值。如果达到超时,则返回空的唯一\u ptr。
std::unique_ptrpop({实现特定类型}超时);
无效推送(标准:唯一性);
诸如
exist()
和front()
之类的功能自然会成为竞争条件的受害者,因为它们无法自动执行您(认为您)想要的任务exist()
有时会在收到结果时返回一个不正确的值,如果队列为空,则必须抛出front()
。我认为empty()
函数无效/危险的原因很清楚。如果需要阻塞队列,请删除该队列
相反,添加一个条件变量(boost::condition,IIRC)。push/pop的功能如下所示:
void push(T data)
{
scoped_lock lock(mutex);
queue.push(data);
condition_var.notify_one();
}
data pop()
{
scoped_lock lock(mutex);
while(queue.empty())
condition_var.wait(lock);
return queue.pop();
}
请注意,这是伪ish代码,但我相信您可以解决这个问题。也就是说,建议使用unique_ptr(或C98的auto_ptr)来避免复制实际数据是一个好主意,但这是一个完全独立的问题。这里有多个代码片段,他们说哪一个有竞争?。。。这里的细节非常重要。请发布您正在使用的实际代码。他的意思是实现阻塞并发队列。如果队列在尝试从中弹出项目时为空,该怎么办?这是一个典型错误,需要更改队列的接口。您可以返回空值或引发异常。或者,您可以阻止,直到一个值变为可用,但在这种情况下,除了互斥量之外,您还需要一个条件变量,除非您想轮询。我认为您的解释没有反映出询问者提交的代码。或者你想告诉我,互斥锁的作用域不包括函数调用吗?它只包括函数调用,但它必须包括对
empty()
和front()
的调用,而这两个调用都不包括。@HassanTM-单个调用受到保护,但在调用empty()或front()和使用结果之间,队列可以改变(参见Nawar的答案)。好吧,我更喜欢阻塞队列,但我会满足于不。。。我只是想从主线程中添加一些作业,然后使用几个工作线程检索和处理它们。我认为您的解释没有反映出询问者提交的代码。或者你想告诉我,互斥锁的作用域不包括函数调用吗?@HassanTM:它确实解释了代码。读这篇文章。问题本身提供了链接。我说的是作者试图在他的文章中解释的东西。