单生产者多个消费者循环缓冲区

时间:2016-02-02 10:40:13

标签: c++ multithreading buffer circular-buffer

在我目前的应用中,我通过光谱仪接收光谱数据。该数据累积一秒钟,然后放入循环缓冲区。现在我有一个消费者,他从缓冲区中弹出条目然后将所有内容保存到磁盘。好的,所有这些东西都有效。现在我需要做的是添加另一个消费者,他与保存并行,对光谱进行一些处理。所以我有两个消费者需要完全相同的数据(注意:他们只阅读并且不会修改)。好的,但这不起作用,因为如果其中一个消费者弹出缓冲区的一个条目,它就会消失,所以另一个不会收到它。我想这个问题的最简单的解决方案是给每个消费者它自己的循环缓冲区。很好,但唯一的问题是:数据条目很大。一个条目的最大大小约为80MB,因此为了节省内存,两次没有相同的数据会很棒。有没有更好的解决方案?

注意:我正在使用循环缓冲区,因此确保缓冲区有一个不断增长的限制。

2 个答案:

答案 0 :(得分:1)

我希望您能直接将数据接收到队列中,而不是将其复制到很多位置....

任何保留单个数据副本的有效解决方案都必须同步所有消费者,这样只有当他们完成所有消息后才能弹出它。

您可以保留循环缓冲区。只需一个移除器即可在读者完成后删除条目。我强烈建议将 remover 作为数据的 writer 。这样,它就是唯一具有队列写入权限的人,这简化了事情。

可以从消费者那里获取卸妆器,告诉他们他们做了什么。

消费者可以与卸妆人分享他们的阅读补偿。您可以在使用者端使用atomic_store,在卸载端使用atomic_load。

应该是这样的:

struct Consumer {
  ...
  long offset = 0;
  ...
  Consumer() {
    q.remover->add(this);
  }
  ...
  void run() {
    for(;;) {
      entry& e = q.read( offset );
      process( e );
      atomic_store( &offest, offset + e.size() );
    }
  }
};

struct Remover {
  ...
  long remove_offset = 0;
  std::list<Consumer*> cons;
  ...
  void remove() {
    // find lowest read point
    long cons_offset = MAX_LONG;
    for( auto p : cons ) {
      cons_offset = std::min( cons_offset, atomic_load(&p->offset) );
    }
    // remove up to that point
    while( cons_offset > remove_offset ) {
      entry& e = q.read(remove_offset);
      remove_offset += e.size();
      q.remove( e.size() );
    }
  }
};

答案 1 :(得分:0)

在缓冲区中保留两个不同的尾指针,每个使用者一个。生产者更新队列时,请使用最远的尾部指针(滞后的尾部指针)检查缓冲区是否已满。使用者可以使用自己的尾指针检查缓冲区是否为空。这样,我们得到了一个无锁的缓冲区,并且没有数据的复制。

有关此解决方案的性能改进的讨论,请参见干扰源交换的实现。