单一生产者和多个单线程消费者

时间:2015-01-21 00:13:24

标签: c++ concurrency

我的应用程序从网络接收数据包并将它们发送到一个或多个处理器"。 (每个数据包都属于预定义的"流"可以通过查看数据包数据来识别。)

目前有一个线程可以完成所有工作:

  1. 从网络设备获取数据包
  2. 识别每个数据包的处理器
  3. 将数据包分发到其处理器
  4. 以每秒2000万个数据包的速率接收传入数据(10个字节的60字节数据包。)

    然而,该解决方案只能跟上极少数的流和处理器。例如,在10个流的情况下,已经有大约10-20%的数据包丢失。

    由于步骤(3)是最昂贵的,我计划将该工作委托给工作线程池。

    但是,我必须小心,因为处理器本身不是线程安全的。因此,只有一个工作线程可以同时将数据包分派到同一个处理器。

    这似乎是基于任务的编程的一个很好的用例。但我无法轻易地将TBB文档中解释的设计模式与我的问题相匹配。

    所以我的问题是:我如何组织我的消费者线程,以便他们将数据包均匀地分发到单线程处理器?

    我不期待一个完全成熟的解决方案,但我会很满意你的建议或随意的想法:)

3 个答案:

答案 0 :(得分:5)

我已经做了一些嵌入式编程,我必须处理相对较高的吞吐量 - 没有你在这里那么快!希望你能够使用比我以前更强大的硬件......有一些简单的策略应该适用于你的情况!

1。输入/处理队列和相关的内存管理至关重要。

如果您具有高数据速率,则传入数据的队列必须非常高效。您应该尽可能少地进行处理,否则可能会丢失设备中的数据。 (我习惯于使用相对较小的缓冲区从某种快速串行设备读取数据,因此实时限制设备可以在不丢失数据的情况下保留多长时间而不会丢失数据。这让我进入了处理从设备读取的习惯是一个完全独立的任务,只处理读取数据而不是其他任何事情。)

一系列非常简单的固定大小的预分配缓冲区效率与它一样高效:拥有一个“免费”的队列。缓冲区和填充的队列'缓冲区。如果使用无锁链接列表,则维护这些列表的速度非常快,并且许多操作系统中的入队/出队操作非常常见。

避免使用malloc或其他动态分配,因为当他们需要管理他们自己的“免费”数据结构时,他们有很多(通常是不可预测的)开销。和'分配'块。如果它们在同一时间内释放或分配内存,它们也可能执行可能无法预测地阻塞生产者或工作者线程的锁。相反,尝试找到较低级别的例程来分配和释放操作系统为您的队列提供的整个页面(mix on unixy-platforms,VirtualAllocEx)。这些通常必须做很少的工作,因为他们使用MMU功能来映射RAM的物理页面,并且在内存中没有复杂的数据结构来维护,每次调用都有更可靠的运行时,并且可以足够快,以便在您的免费列表运行不足时扩展它。

在制片人中,不要担心比整个街区小的单位。从队列中取出一个空闲块,打包一个装满数据的块,将其添加到要处理的队列中。如果您必须确保在固定的时间段内处理每个数据包,或者您需要处理' bursty'数据速率,然后仍然尝试从您的输入设备读取一个完整的缓冲区,但要么将块的大小减小为合理的'时间量,或使用超时并将部分填充的块排入队列并填充'带有某种空包的余数。我发现这样做通常比包含大量用于处理部分填充缓冲区的代码更快。

如果可以,请非常仔细地设置生产者线程的处理器关联和线程优先级。理想情况下,您希望生产者线程具有比任何使用者线程更高的优先级,并且与特定核心绑定。没有什么可以防止在缓冲区空间不足的情况下读取传入数据。

2。处理

您已经说过:

  1. 几个流
  2. 多个处理器',它们不是线程安全的
  3. 这里有用的是在数据包上并行运行处理器,但是从你的问题到可能的程度并不清楚。

    处理器是否是跨线程的线程安全的? (只要它们在两个不同的流上运行,我们可以在两个不同的线程中运行处理器吗?)

    处理器是否在同一个流中的不同处理器之间是线程安全的? (我们可以在单独的线程中在同一个流上运行多个处理器吗?)

    处理器是否需要按特定顺序运行?

    在不知道这一点的情况下,还有一些通用的东西是有用的建议。

    有第二个线程正在处理从生产者读取完整缓冲区并将它们分派到适当的处理器(在其他线程中),然后将完整的缓冲区放回到空的'队列进行处理。虽然你会失去一些直线效率(一个线程正在进行读取和调度会略微超过两个),但至少这种方式不会阻止从输入设备读取,如果有的话。一时的锁定。

    创建或查找允许您将作业分配给线程池的库,特别是如果您有多个处理器与可以并行运行的线程数相比较。实现某种工作排队相对简单,允许工作之间的一些简单的关系(例如,#34;这项工作要求工作X和Y先完成","这个job不能与使用相同处理器的任何其他作业并行运行")。即使是一个简单的策略,其中作业管理器只是在第一个可用线程上运行第一个可运行的作业,这可能非常有效。

    尽量避免复制。如果处理器可以就地处理数据包'没有从缓冲区复制它们,那么你已经节省了很多无意义的周期。即使您必须复制,也有几个线程从“只读”复制数据。共享缓冲区比单个线程复制并将消息分派给多个线程更好。

    如果检查是否应该为给定数据包运行处理器的速度非常快,那么您可能最好有几个工作,每个工作都检查是否应该进行一些处理。不是让单个线程弄清楚哪些处理器应该在哪些数据包上运行,而是拥有多个线程(每个处理器或处理器组一个),检查每个数据包一次是否应该运行处理器可能更快。这可以归结为这样的想法:在多个线程中多次检查只读资源可能比在线程之间进行同步所花费的时间更少。

    如果他们可以并行运行处理器,如果他们正在处理来自不同流的数据,那么执行传递数据以获取流的列表然后为每个流启动作业是一个好主意。您还可以收集属于每个流的数据包列表,但同样,它需要在作业检查每个数据包的速度与在单线程中收集该列表所需的时间之间进行权衡。将每个人分别转移到各自的岗位上。

    希望其中一些策略对您的情况有用!让我们知道它是如何运作的......这是您需要处理的大量数据,并且知道什么是有效的并且不会有效比以前更快的数据速率!祝你好运!

答案 1 :(得分:1)

这是我对可能解决方案的想法。

我们说我们有n个处理器。让我们介绍n个互斥体,每个处理器一个。我们还要为数据包引入一个队列。所有传入的数据包都被放入此队列。

工作线程的运作方式如下:

  1. 从传入的数据包队列中抓取数据包。
  2. 确定必要的处理器。
  3. 尝试获取相应的互斥锁。如果锁定获取成功,则处理该数据包。否则,重新入队并转到1。
  4. 处理完成后,请转到步骤1.
  5. 可能的缺点:

    1. 数据包重新排队,这意味着它们可能会被无序延迟/处理,这对您来说可能是一个交易破坏者(不确定)。
    2. 队列上的争用可能很高。您可能希望考虑使用无锁队列。
    3. 队列显然消耗额外的内存,我不知道你是否有备用内存。
    4. 编辑:关于内存消耗的更多想法 - 当然,它可以对队列可以消耗的内存量设置上限 - 然后,问题是当内存不足时该怎么办。我想说最好的办法就是开始丢弃数据包(我觉得在你的情况下放弃一些并不是什么大不了的事),直到队列耗尽。

      与此有点相关 - 我认为这个用例的良好队列实现应该不惜一切代价避免动态内存分配 - 预先分配内存并确保关键代码路径上没有分配。

答案 2 :(得分:1)

为什么不能使用多个队列,每个处理器一个? 这些队列可以无锁(没有互斥锁)。

  1. 从网络设备获取数据包
  2. 识别每个数据包的处理器(PID)
  3. 将数据包推送到队列[PID]
  4. 一个worker:来自队列[k]
  5. 的进程数据包

    对于类似的问题,我使用无锁环缓冲区的轮询,自动覆盖最旧的数据包。

相关问题