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:它确实解释了代码。读这篇文章。问题本身提供了链接。我说的是作者试图在他的文章中解释的东西。