使用condition_variable和unique_lock导致定期崩溃(GCC 4.7,OSX)

时间:2012-03-30 18:43:52

标签: c++

我有一些代码可以从C ++ Concurrency In Action一书中修改(几乎没有改变它),所以本来希望它可以工作 - 只有它没有。 我试图做的是实现一个线程安全的队列,然后我可以为一个或多个线程存储后台作业。 队列如下所示:

queue.h

#pragma once
#include "imgproc/i_queue.h"
#include <memory>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>

using namespace std;

namespace imgproc {

  /* Blocking, concurrency-safe queue. The method that blocks is pop(),
   * which makes the current thread wait until there is a value to pop
   * from the queue.
   */
  template <typename T>
    struct ConcurrentQueue : public IQueueWriter<T>, public IQueueReader<T>
    {
      ConcurrentQueue() {}
      ConcurrentQueue( const ConcurrentQueue & ) = delete;
      ConcurrentQueue & operator= ( const ConcurrentQueue & ) = delete;

      /* Concurrency-safe push to queue.
       */
      virtual void push( shared_ptr<T> val )
      {
        lock_guard<mutex> lk( _mutex );
        _queue.push( val );
        _cvar.notify_one();
      }

      /* Concurrency-safe check if queue empty.
       */
      virtual const bool empty() const
      {
        lock_guard<mutex> lk( _mutex );
        bool result( _queue.empty() );
        return result;
      }

      /* Waiting, concurrency-safe pop of value. If there are no values in
       * the queue, then this method blocks the current thread until there
       * are.
       */
      virtual shared_ptr<T> pop()
      {
        unique_lock<mutex> lk( _mutex );
        _cvar.wait( lk, [ this ] {return ! _queue.empty(); } );
        auto value( _queue.front() );
        _queue.pop();
        return value;
      }

    private:
      mutable mutex _mutex;
      queue<shared_ptr<T>> _queue;
      condition_variable _cvar;
    };

}

我的理解是那里的一个互斥锁应该保护所有访问队列的尝试。但是,我有一个测试,在10中崩溃了大约一次:

测试该-崩溃-fragment.cpp

// Should have threads wait until there is a value to pop
TEST_F( ConcurrentQueueTest,
        ShouldHaveThreadsWaitUntilThereIsAValueToPop ) {
  int val( -1 );
  thread t1( [ this, &val ] {
      for ( uint i( 0 ) ; i < 1000 ; ++i );
      val = *_r_queue->pop();
    } );
  for ( uint i( 0 ) ; i < 1000 ; ++ i ) {
    for ( uint j( 0 ) ; j < 1000 ; ++ j );
    EXPECT_EQ( -1, val );
  }
  _w_queue->push( make_shared<int>( 27 ) );
  t1.join();
  EXPECT_EQ( 27, val );
  EXPECT_TRUE( _r_queue->empty() );
}

变量_r_queue_w_queue只是同一个ConcurrentQueue实例上的接口。

从花费大量时间调试信息开始,看起来pop()的调用是导致崩溃的原因,当_queue member实例变量为空时,总是(我已经看到)。 在这里,有人能给我任何关于我做错的建议吗?我已经看到其他帖子在类似问题上寻求帮助,但他们似乎总是说条件变量就是答案 - 我正在尝试!

或者可能有一些关于如何更好地调试这个以帮助我解决它的建议? FWIW,我尝试手动实现一个while并加上sleep( 1 ),并且仍然会定期崩溃,这表明尽管我付出了最大的努力,我仍然设法获得了竞争条件 - 只有我真的可以看不到。

非常感谢任何&amp;一切都有所帮助,我保证在尝试使用它之前我已经试图解决这个问题。

干杯,    道格。

1 个答案:

答案 0 :(得分:1)

通过阅读https://gist.github.com/2396866我发现问题出在测试 //应该能够并发弹出值。创建两个线程然后分离。两个线程都保持弹出即使在测试结束后,也会无限期地排队。这会影响最后一次测试(问题出现在哪里)。

快速解决方案是:

/* ... */

{ // Should be able to concurrently pop values
  for ( uint i( 0 ) ; i < 100 ; ++ i )
    q.push( make_shared<string>( "Monty Halfwit" ) );

  pair<uint, uint> counts( 0, 0 );
  thread t1( [ & ] {
    while ( ++counts.first != 50 ) {
      this_thread::sleep_for( chrono::milliseconds( 1 ) );
      q.pop();
    }
  });

  thread t2( [ & ] {
    while ( ++counts.second != 50 ) {
      this_thread::sleep_for( chrono::milliseconds( 1 ) );
      q.pop();
    }
  });

  t1.detach();
  t2.detach();

/* ... */

这将使线程在每次弹出50个字符串时死亡。