2013-01-13 46 views
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(); 
    } 
}; 
+0

那里有一个以上的代码片段,他们说哪一个代码片段有比赛? – hmatar

+0

...细节在这里非常重要。请发布您正在使用的实际代码。 – Mat

+0

他的意思是实现*阻塞*并发队列。如果队列在使用时试图从中弹出项目,那该怎么办? – Nawaz

回答

0

如果使用空并找到队列不为空,另一个线程可能会突然出现使得空的使用结果之前的项目。

与前面类似,您可以阅读前面的项目,并且在您使用该项目时,它可能会被另一个线程弹出。

+0

这是一个典型的错误,它需要将接口更改为队列。您可以返回空值或抛出异常。或者,您可以阻止,直到有一个值可用,但在这种情况下,除了互斥锁之外,您还需要一个条件变量,除非您想要轮询。 –

+0

我认为你的解释并不反映提问者提交的代码。或者你想告诉我,the_mutex的范围不包括函数调用? – hmatar

+0

它只包含函数调用,但它必须包含对'empty()'和'front()'的调用,而不是。 –

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

嗯,我宁愿一个阻塞队列,但我会解决一个不......我只是想从主线程放一些工作,然后使用几个工作线程检索和处理它们 – hfhc2

+0

@ hfhc2:现在查看我的代码。 – Nawaz

+0

我认为你的解释并不反映提问者提交的代码。或者你想告诉我,the_mutex的范围不包括函数调用? – hmatar

0

从@parkydr和@Nawaz的答案是正确的,但这里的另一个回味无穷;

你想达到什么目的?

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

的原因之一,线程安全队列是消费者 - 生产者情况,其中1-N节点推送数据,也不管他们得到了什么1-M节点从中弹出。队列中的所有元素都是平等的,消费者只是弹出而不知道他们得到了什么,并开始处理数据。在这种情况下,你的界面不应该公开T& front()。那么,如果你不确定哪里有物品(如果没有外部锁定,你永远不能确定),你永远都不应该返回一个参考。我会推荐使用unique_ptr的(或当然是shared_ptr),并且只暴露种族自由功能(为了简洁起见,我将忽略const函数)。使用std::unique_ptr需要C++ 11,但你可以使用boost::shared_ptr为相同的功能,如果C++ 11是不可能让你使用:

// 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()必须抛出。

0

我想为什么empty()函数是无用的/危险的答案很清楚。如果你想要一个阻塞队列,请删除它。

相反,添加一个条件变量(boost :: condition,IIRC)。功能推/然后在弹出如下图所示:

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(或auto_ptr for C98)来避免复制实际数据是一个好主意,但这是一个完全独立的问题。