【问题标题】:Race condition in a concurrent queue并发队列中的竞争条件
【发布时间】:2013-01-13 17:29:49
【问题描述】:

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

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

【问题讨论】:

  • 那里有不止一个代码sn-ps,他们说哪个有比赛?
  • ...细节在这里非常重要。请发布您正在使用的实际代码。
  • 他的意思是实现阻塞并发队列。如果队列在他们尝试从中弹出项目时为空怎么办?

标签: c++ concurrency queue race-condition


【解决方案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?)
}

【讨论】:

  • 好吧,我更喜欢阻塞队列,但我会选择一个不支持的队列...我只想从主线程中放置一些作业,然后使用检索和处理它们几个工作线程
  • 我认为您的解释没有反映提问者提交的代码。还是你想告诉我the_mutex的作用域不包括函数调用?
  • @HassanTM:它确实解释了代码。阅读文章。该链接在问题本身中提供。我说的是作者试图在他的文章中解释的内容。
【解决方案2】:

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

同样的front,你可以读到最前面的item,在你使用这个item的时候,它可能会被另一个线程弹出。

【讨论】:

  • 这是一个经典错误,需要将接口更改为队列。您可以返回空值或引发异常。或者,您可以阻塞直到某个值可用,但在这种情况下,除了互斥锁之外,您还需要一个条件变量,除非您想轮询。
  • 我认为您的解释没有反映提问者提交的代码。还是你想告诉我the_mutex的作用域不包括函数调用?
  • 它只包括函数调用,但它必须包括对empty()front()的调用,它没有。
  • @HassanTM - 单个调用受到保护,但在调用 empty() 或 front() 并使用结果之间,队列可能会改变(参见 Nawar 的回答)。
【解决方案3】:

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

你想达到什么目的?

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

然而,线程安全队列的一个原因是 consumer-producer 情况,其中 1-N 个节点推送数据,而 1-M em> 节点无论得到什么,都会从中弹出。队列中的所有元素都被平等对待,消费者只是弹出而不知道他们得到了什么,然后开始处理数据。在这种情况下,您的界面不应公开T&amp; 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() 将不得不抛出。

【讨论】:

    【解决方案4】:

    我认为为什么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)来避免复制实际数据的建议是个好主意,但这是一个完全独立的问题。

    【讨论】:

      猜你喜欢
      • 2019-07-04
      • 1970-01-01
      • 1970-01-01
      • 2023-01-30
      • 2010-10-30
      • 1970-01-01
      • 2015-08-01
      • 2013-12-31
      • 2013-01-14
      相关资源
      最近更新 更多