生产者完成后通知消费者的优雅方式?

时间:2012-02-27 12:40:28

标签: c++ multithreading concurrency queue producer-consumer

我正在实现一个功能最少的concurrent_blocking_queue

//a thin wrapper over std::queue
template<typename T>
class concurrent_blocking_queue
{
    std::queue<T> m_internal_queue;
     //...
 public:
     void add(T const & item);
     T&   remove();
     bool empty();
};

我打算将它用于producer-consumer problem(我想,这是使用这种数据结构的地方吗?)。但我坚持一个问题是:

生产者完成后如何优雅地通知消费者?生产者完成后如何通知队列?通过调用特定的成员函数,说done()?从队列中抛出异常(即来自remove函数)是一个好主意吗?

我遇到了很多例子,但都有无限循环,好像生产者将永远生产物品。没有讨论停止条件的问题,甚至没有讨论wiki article

4 个答案:

答案 0 :(得分:5)

我过去只是介绍了一个虚拟的“完成”产品。因此,如果生产者可以创建类型A和类型B的“产品”,我发明了“完成”类型。当消费者遇到“完成”类型的产品时,它知道不再需要进一步处理。

答案 1 :(得分:2)

确实,将一个特殊的&#34;我们已经完成&#34;排队等等是很常见的。信息;但我认为OP对带外指标的原始愿望是合理的。看看人们正在考虑设置带内完成消息的复杂性!代理类型,模板;好悲伤。我说done()方法更简单,更简单,它使常见的情况(我们还没有完成)更快更清洁。

我同意kids_fox的说法,如果队列完成则返回错误代码的try_remove是首选,但那样是风格和YMMV。

编辑: 用于实现队列的奖励积分,该队列记录多生产者情况下剩余的生产者数量,并且如果所有生产者已经抛弃,则引发完成的异常;-)不会执行 带有带内消息!

答案 2 :(得分:1)

'停止'并不经常讨论,因为它经常从未完成。在需要它的情况下,使用更高级别的P-C协议本身将毒丸排队更容易和更灵活,因为它是在队列本身中构建额外的功能。

如果你真的想要这样做,你确实可以设置一个标志,让每个消费者引发异常,“立即”或每当它回到队列时,但是有问题。你需要'done'方法是同步的,即。你想让所有的消费者在'完成'回归时消失,或者异步,即。当所有其他消费者都消失时,最后一个消费者线程调用一个事件参数?

您如何安排那些正在等待醒来的消费者?有多少人正在等待,有多少人在忙,但是当他们完成工作后会回到队列中?如果一个或多个消费者陷入阻塞呼叫,(或许他们可以解锁,但这需要来自另一个线程的呼叫 - 你打算怎么做)怎么办?

消费者如何通知他们已处理异常并即将死亡?是“即将死”,还是需要等待线程处理?如果你必须等待线程句柄,那么等待什么 - 请求队列关闭的线程或最后一个消费者线程要通知?

哦,是的 - 为了安全起见,您应该安排生产者线程,这些线程会在“关闭”状态下与对象一起排队,以便引发异常。

我提出这些问题,因为我很久以前就完成了这一切。最终,这一切都奏效了。排队的对象都必须在其继承链中插入“QueuedItem”(以便可以将作业取消方法暴露给队列),并且队列必须保留一个线程安全的对象列表。弹出 线程但尚未处理。

过了一会儿,我停止使用该类,而不使用特殊的关闭机制的简单P-C队列。

答案 3 :(得分:1)

我的队列通常使用指针(在{。}}中使用std::auto_ptr 界面,以清楚地表明发件人可能不再访问 指针);在大多数情况下,排队的对象是多态的,所以 无论如何都需要动态分配和引用语义。 否则,添加“结尾”应该不会太难 file“标志到队列。你需要一个特殊的功能 生产者方(close?)来设置它(使用完全相同的锁定 当你写入队列时的原语)和删除中的循环 函数必须等待存在的东西或队列 关闭。当然,您需要返回Fallible值,这样 读者可以知道读取是否成功。另外,不要 忘记在这种情况下,你需要一个notify_all来确保所有 等待条件的进程被唤醒。

BTW:我不太清楚你的界面是如何实现的。是什么 T&返回的remove是指。基本上,remove必须是。{1}} 类似的东西:

Fallible<T>
MessageQueue<T>::receive()
{
    ScopedLock l( myMutex );
    while ( myQueue.empty() && ! myIsDone )
        myCondition.wait( myMutex );
    Fallible<T> results;
    if ( !myQueue.empty() ) {
        results.validate( myQueue.top() );
        myQueue.pop();
    }
    return results;
}

即使没有myIsDone条件,您也必须将值读入 局部变量在从队列中删除之前,你不能返回一个 引用局部变量。

其余的:

void
MessageQueue<T>::send( T const& newValue )
{
    ScopedLock l( myMutex );
    myQueue.push( newValue );
    myCondition.notify_all();
}

void
MessageQueue<T>::close()
{
    ScopedLock l( myMutex );
    myIsDone = true;
    myCondition.notify_all();
}