0

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

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

4 回答 4

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?)
}
于 2013-01-13T17:43:07.010 回答
0

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

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

于 2013-01-13T17:39:20.813 回答
0

@parkydr 和 @Nawaz 的答案是正确的,但这里有另一个值得深思的地方;

你想达到什么目的?

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

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

我建议使用unique_ptr's (或者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()如果队列为空,则必须抛出。

于 2013-01-13T18:11:42.267 回答
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)来避免复制实际数据的建议是个好主意,但这是一个完全独立的问题。

于 2013-01-13T18:18:06.447 回答