并发队列中的竞争条件

时间:2013-01-13 17:29:49

标签: c++ concurrency queue race-condition

我目前正在尝试编写一个并发队列,但我有一些我无法向自己解释的段错误。我的队列实现基本上由该站点上的第一个列表给出。

http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.html

网站说如果对象从队列中并行删除就会出现竞争情况,但我只是不明白为什么会有这样的情况,有人可以向我解释一下吗?

编辑:这是代码:

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();
    }
};

4 个答案:

答案 0 :(得分:1)

如果您尝试从中读取项目时队列为空,该怎么办?

想想这个用户代码:

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?)
}

答案 1 :(得分:0)

如果您使用空并且发现队列不为空,则在您使用结果之前,另一个线程可能会弹出该项,使其变空。

同样对于前面,您可以阅读前面的项目,并且在您使用该项目时它可以被另一个线程弹出。

答案 2 :(得分:0)

@ parkydr和@Nawaz的回答是正确的,但这是另一种思考的食物;

你想要实现什么目标?

有时候(我不敢说经常)错误地拥有一个线程安全队列的原因。在许多情况下,您希望在队列只是实现细节的上下文中“锁定”队列“。”

一个原因是,线程安全队列用于消费者 - 生产者情况,其中 1-N 节点推送数据, 1-M 节点从中弹出,无论它们得到什么。队列中的所有元素都被视为相等,并且消费者只是在不知道他们得到什么的情况下弹出,并开始处理数据。在这种情况下,您的界面不应公开T& front()。好吧,如果你不确定那里有一个项目,你永远不应该返回一个参考(在并行情况下,如果没有外部锁定,你永远不能确定。)

我建议使用unique_ptr(或当然是shared_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 );

exist()front()等功能自然会成为竞争条件的受害者,因为它们无法自动执行您(您认为)想要的任务。 exist()有时会在收到结果时返回一个不正确的值,如果队列为空,则front()必须抛出。

答案 3 :(得分:0)

我认为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();
}

请注意,这是伪代码,但我相信你可以解决这个问题。也就是说,建议使用unique_ptr(或C98的auto_ptr)来避免复制实际数据是一个好主意,但这是一个完全独立的问题。