C++中的多线程池

Multithreading Pool in C++

本文关键字:多线程 C++      更新时间:2023-10-16

我正在开发一个程序,该程序连续接收来自视频流的帧并计算每对帧之间的运动估计值。

由于硬件限制,我必须在CPU中计算运动估计(ME)算法,每次计算大约需要2秒。因此,我想通过多线程实现 ME 算法。这个想法是在主线程中从流中接收下一帧,同时在其他线程中计算运动值。

我为每个任务使用一个线程来完成此操作,也就是说,每次收到一对帧时,我都会创建一个新线程来计算运动值。但是,由于运动计算中经过的时间,许多线程被创建并同时运行,我认为这不是很有效。

我认为重新实现这一点的最佳方法是使用线程池。例如,一方面有一个主线程接收帧并将它们存储在缓冲区或队列中,另一方面具有 4 或 8 个线程并发运行并从接收缓冲区读取,如果我没错的话,应该受到互斥锁的保护。但是,主线程接收帧的速度比一个运动计算结束的速度快得多,我不知道如何管理它。

我对C++和线程都很陌生,所以如果您能为我提供一些伪代码解决方案以开始我的重新实现,我将不胜感激。

非常感谢

在这种情况下,我会避免使用线程池。来自维基百科(强调我的):

[线程池] 可提高性能并避免由于频繁创建和销毁短期任务的线程而导致的执行延迟。

长时间运行的计算使创建和销毁线程所需的时间相形见绌,因此为每个任务创建一个线程对我来说似乎是合理的。越能避免互斥锁和 co. 越好。至于一次运行大量线程,在线程之间切换所需的时间与计算时间相比也相形见绌,因此限制使用的线程数只会给您一个非常小的加速1

您可能会遇到问题的地方是,如果您的机器无法足够快地完成计算以跟上传入的数据。如果你所有的CPU内核都以100%的速度运行,你唯一能做的就是让你的计算更有效率(也许对你的视频帧进行降采样?)或获得更多的计算能力。


它们是以 30fps 实时传入的帧。

1我应该注意,对于实时应用程序,您应该将使用的线程数限制为内核数(或更高的一两个,对其进行分析)。这将减少接收帧和生成结果之间的延迟,而不会影响整体性能。

粗略的可行性估计

。每次计算大约需要 2 秒。

。有 4 或 8 个线程同时运行...

。约 5-6 帧/秒

好吧,这些限制显然是行不通的。

八个线程每秒产生 0.5 帧,每秒最多提供 4 帧。

如果每秒需要 6 帧,则需要 12 个线程。此外,这些线程实际上需要绑定到真正的硬件内核。

接下来,您需要描述您的硬件平台。如果它没有至少 12 个内核,你就无法按照你的要求去做,至少按照你建议的方式。

如果它有 12 个超线程"内核",这可能也不够:一个线程可能会使所有 ALU 饱和。你还没有说你的框架有多大,但L1压力也可能是一个问题。

如果没有那么多内核,则需要更快地计算每个帧,或者折衷于每秒输出帧数。

实现

你说你想估计两个连续帧之间的运动。这是否意味着连续的输入帧,还是连续的输出帧?

第一种情况意味着您正在对输入进行采样,为每个输出读取两个帧,这是更多的数据,但您的线程可以并行进行:

输出0= ME(输入0,输入1)

输出1= ME(在 6,在7)

(或ME(0,6), ME(6,12), ...什么的)。

第二种情况意味着每个输出只需要一个输入帧,但在第一个输出帧完成之前,您无法启动第二个输出帧(您将第一个输出与第 n 个输入帧进行比较):

输出0= 输入0

输出1= ME(输出0,输入6)

输出2= ME(输出1输入 12)

>吨;dr在真正开始编写任何内容之前,您需要澄清一些基本事项。

您应该查看英特尔 TBB 任务调度程序。您将每个计算设置为一个任务(具有execute函数的派生类),并让调度程序在可用的 CPU 上为您调度它。

任务与逻辑线程

的主要优势在于任务比逻辑线程轻得多。在 Linux 系统上,启动和终止任务比启动和终止线程快约 18 倍。在 Windows 系统上,该比率超过 100。这是因为线程有自己的大量资源副本,例如寄存器状态和堆栈。在 Linux 上,线程甚至有自己的进程 ID。相比之下,英特尔®线程构建模块中的任务通常是一个小例程,也不能在任务级别抢占(尽管其逻辑线程可以被抢占)。

调度程序执行负载平衡。除了使用正确数量的线程外,在这些线程之间均匀分配工作也很重要。只要您将程序分解为足够小的任务,调度程序通常就可以很好地将任务分配给线程以平衡负载。使用基于线程的编程,您经常需要自己处理负载平衡,这可能很难正确处理。

最后,使用任务而不是线程的主要优点是,它们可以让您在更高的、基于任务的层面上思考。使用基于线程的编程,您被迫在物理线程的低级别进行思考以获得良好的效率,因为每个物理线程都有一个逻辑线程,以避免订阅不足或超额订阅。您还必须处理相对粗糙的线粒。对于任务,您可以专注于任务之间的逻辑依赖关系,并将高效的调度留给调度程序。


或者,如果您无法使用库,则可以根据这些想法实现自己的任务计划程序。一个简单的实现是多生产者多使用者队列,由池中固定数量的长期线程提供服务(对于计算线程池,您不希望线程数超过可用 CPU 内核数)。空闲线程将等待队列,当一个任务可用时获取任务并执行它。