繁体   English   中英

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

[英]Single producer and multiple single-threaded consumers

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

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

  1. 从网络设备获取数据包
  2. 识别每个数据包的处理器
  3. 将数据包分发到其处理器

以每秒2000万个数据包的速率接收传入数据(10个字节的60字节数据包)。

然而,该解决方案仅能够跟上非常少量的流和处理器。 例如,在10个流的情况下,已经有大约10-20%的分组丢失。

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

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

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

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

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

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

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

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

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

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

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

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

2.处理

你说过有:

  1. 几个流
  2. 几个“处理器”,它们不是线程安全的

这里有用的是并行处理数据包上的处理器,但是从你的问题中可以清楚地知道这可能的程度。

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

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

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

如果不知道这一点,仍然有一些通用的东西是有用的建议。

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

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

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

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

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

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

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

假设我们有n个处理器。 我们来介绍n个互斥体,每个处理器一个。 我们还介绍一个数据包队列。 所有传入的数据包都放入此队列。

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

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

可能的缺点:

  1. 数据包重新排队,这意味着它们可以被无序延迟/处理,这对您来说可能是一个交易破坏者(不确定)。
  2. 队列上的争用可能很高。 您可能希望考虑使用无锁队列。
  3. 队列显然会消耗额外的内存,我不知道你是否有备用内存。

编辑:更多关于内存消耗的想法 - 当然,它可以对队列可以消耗的内存量设置上限 - 然后,问题是当内存不足时该怎么办。 我会说最好的办法就是开始丢弃数据包(我得到的印象是,在你的情况下丢弃一些并不是什么大问题),直到队列耗尽。

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM