【问题标题】:What are the problems with this producer/consumer implementation?这个生产者/消费者实现有什么问题?
【发布时间】:2011-03-17 17:19:51
【问题描述】:

所以我正在考虑在 C++ 中使用简单的生产者/消费者队列。我最终将使用 boost 进行线程处理,但这个示例只是使用 pthreads。我最终也会使用一种更加面向对象的方法,但我认为这会掩盖我目前感兴趣的细节。

无论如何,我担心的具体问题是

  1. 由于此代码使用 std::deque 的 push_back 和 pop_front - 它可能在不同线程中分配和释放底层数据 - 我认为这是不好的(未定义的行为) - 避免这种情况的最简单方法是什么?
  2. 没有任何东西被标记为易失性。但是重要的位是互斥保护的。我是否需要将任何内容标记为易失性,如果需要,怎么办? - 我不认为我这样做,因为我相信互斥体包含适当的内存屏障等,但我不确定。

还有其他明显的问题吗?

代码如下:

#include <pthread.h>
#include <deque>
#include <iostream>

struct Data
{
  std::deque<int> * q;
  pthread_mutex_t * mutex;
};

void* producer( void* arg )
{
  std::deque<int> &q = *(static_cast<Data*>(arg)->q);
  pthread_mutex_t * m =  (static_cast<Data*>(arg)->mutex);

  for(unsigned int i=0; i<100; ++i)
  {
    pthread_mutex_lock( m );
    q.push_back( i );
    std::cout<<"Producing "<<i<<std::endl;
    pthread_mutex_unlock( m );
  }
  return NULL;
}

void* consumer( void * arg )
{
  std::deque<int> &q = *(static_cast<Data*>(arg)->q);
  pthread_mutex_t * m =  (static_cast<Data*>(arg)->mutex);

  for(unsigned int i=0; i<100; ++i)
  {
    pthread_mutex_lock( m );
    int v = q.front();
    q.pop_front();
    std::cout<<"Consuming "<<v<<std::endl;
    pthread_mutex_unlock( m );
  }  
  return NULL;
}

int main()
{
  Data d;

  std::deque<int> q;
  d.q = &q;

  pthread_mutex_t mutex;
  pthread_mutex_init( &mutex, NULL );
  d.mutex = & mutex;

  pthread_t producer_thread;
  pthread_t consumer_thread;

  pthread_create( &producer_thread, NULL, producer, &d );
  pthread_create( &consumer_thread, NULL, consumer, &d );

  pthread_join( producer_thread, NULL );
  pthread_join( consumer_thread, NULL );
}

编辑:

我最终放弃了这个实现,我现在使用 Anthony Williams 的 here 代码的修改版本。我的修改版可以找到here这个修改版使用了更明智的基于条件变量的方法。

【问题讨论】:

  • 主要问题是您要求我们评估一个解决方案,您将撕毁并丢弃底层线程,然后还以“更多OO”的方式。我认为这被称为过早评估:-)
  • @paxdiablo:他的具体问题确实有其优点。但是对于幽默的术语 +1...
  • @paxdiablo 我只是想避免“使用对象”或“使用增强”形式的无用答案。我很清楚在迁移代码时可能会遇到其他问题 - 但我在此处获得的答案将在修改后的代码中保持相关性。
  • stackoverflow.com/questions/2363888 回答分配问题。摘要与 Amardeep 和 James McNellis 提到的内容相匹配……附带一个小条件,即由于标准未提及线程,行为是实现定义的(即未定义的行为),但所有/大多数当前实现实际上都将其定义为 OK - 这么久当您链接到正确的运行时库时。
  • 我自己,我会说“此时使用对象或提升不是一个选项,所以请不要建议”。这有两件事:(1)让人们意识到你对那些答案不感兴趣; (2) 阻止被称为“pax”的令人讨厌的 yobbos 给您带来困难 :-) 感谢您的澄清。

标签: c++ pthreads


【解决方案1】:

由于此代码使用 std::deque 的 push_back 和 pop_front - 它可能在不同线程中分配和释放底层数据 - 我认为这是不好的(未定义的行为) - 避免这种情况的最简单方法是什么?

只要一次只有一个线程可以修改容器,就可以了。

没有任何东西被标记为易失性。但重要的位是互斥保护的。我是否需要将任何内容标记为易失性,如果需要,怎么办? - 我不认为我这样做,因为我相信互斥体包含适当的内存屏障等,但我不确定。

只要您使用互斥锁正确控制对容器的访问,它就不需要是volatile(这取决于您的线程库,但如果没有,它就不是一个很好的互斥锁提供正确的内存屏障)。

【讨论】:

    【解决方案2】:
    1. 如果两个线程在同一个进程中,在一个线程中分配内存并在另一个线程中释放它是完全有效的。

    2. 使用互斥锁来保护对双端队列的访问应该提供正确的内存访问配置。

    编辑: 唯一需要考虑的是生产者和消费者的性质。您的综合示例缺少与实际实现相关的一些微妙之处。例如,如果生产者与消费者的运行速度不完全相同,您将如何同步它们?您可能需要考虑使用管道或操作系统队列之类的东西而不是双端队列,这样如果没有准备好处理的数据,消费者可以在读取时阻塞。

    【讨论】:

    • 我认为编辑可能属于关键点的区域。提供的示例代码可能/可能/可能会起作用,但确实不能保证当消费者线程启动时队列中会有任何东西。如果发生这种情况,queue.front() 和 queue.pop_front() 将出现不良的未定义行为。
    • @sprong :你说的完全正确。知道何时停止以及何时继续阅读是此设计中的一个关键问题。正如您所注意到的,它目前非常损坏...我只是幸运地运行它,它没有崩溃。认为我可能不得不关闭它并创建一个更详细的案例,
    • @Michael:还有几点。您应该在队列操作完成后立即解锁。假设要完成的工作由cout&lt;&lt; 语句表示,并且执行需要时间。如果队列在整个持续时间内都被锁定,那么生产者将被阻止推入队列,直到消费者完成较早的项目(也就是说,他们不能同时进行有用的工作)。 2. 需要一种停止消费者的机制,即在这个之后没有更多的项目。
    • @rwong - 确实,原始代码有很多错误。我现在正在使用更好的解决方案,并更新了 OP 以提及该解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-23
    • 1970-01-01
    • 1970-01-01
    • 2011-07-27
    相关资源
    最近更新 更多