Python 主线程 cpu 负载较重时防止后台线程 UDP 丢包

Prevent UDP packet loss on background thread when main thread cpu load is heavy in Python

我正在开发一个原型应用程序,主要是为一个研究项目用 Python (2.7) 编写的。该应用程序通过 UDP 从无线设备接收 mpegts 视频流,然后使用 openCV 来 process/analyze 视频帧。 mpeg 解压缩是在我自己的 C 库中完成的,它基本上只是 libavcodec 的 Python 绑定包装器。该应用程序是一个 Python GLUT 应用程序,它使用 GLUT 进行绘图和一些基本的事件处理。

我的问题是,当我的 openCV 处理或绘图代码对 CPU 征税时,我丢失了 UDP 数据包,导致视频帧损坏。我知道 UDP 是一种“不可靠”的协议,丢包是可以预料的,但不幸的是,这是我必须使用的协议。

幸运的是,我有一台运行速度相当快的机器 运行 这台机器 (MacBookPro11,3 2.8GHz 四核 i7)。我的目标是创建一个使用线程和队列的系统,从而优先考虑 UDP 数据包的消耗和视频解压缩,以便每个解压缩的帧都被完整地接收(除非实际网络错误)。如果我的主线程绘图或处理无法跟上视频流帧速率,我想丢弃整个解压缩帧,以便 mpeg 流保持连贯。另一种方法是丢弃单个 UDP 数据包,但这会导致损坏的图像流在一段时间内无法恢复,即。收到下一个 I 帧,我相信。这是我要避免的情况。

我已经创建了这样一个系统,它产生了一个后台线程,它完成了创建视频解压缩上下文和 UDP 套接字的所有工作。后台线程无限循环向解压器询问解码帧,后者又根据需要调用回调,等待来自套接字的更多数据(使用 select、轮询或阻塞接收,我已经尝试了所有这三种)。在收到每个新的解压缩帧后,后台线程将帧添加到队列中,主线程会尽可能快地处理这些帧。如果队列已满,因为主线程消费者跟不上,那么新解压的帧就会被丢弃,进程继续。

我遇到的问题是即使使用这个系统,主线程上的重负载仍然导致 UDP 数据包被丢弃。几乎就好像接收和缓冲传入的 udp 数据包的内核数据包调度程序在我的主线程上 运行,它正在完成视频帧的所有绘制和处理(我只知道我在说什么)在这里,关于数据包调度程序)。如果我在主线程上注释掉所有繁重的处理和绘图代码 运行,那么我将永远不会收到任何丢包/mpeg 解码错误。

我已经尝试过最大化我的套接字接收缓冲区,这在一定程度上有所帮助,但也会增加延迟,这也是不可取的,它最终只会延迟问题。

所以我的问题是,我可以做些什么来确保我的所有 UDP 数据包都被消耗并尽快传递给解压缩器,独立于主线程 cpu 负载?

我尝试将后台线程的线程优先级设置为 1.0,但这没有帮助。 libavcodec 默认生成 9 个线程来处理解压缩,但我可以选择将其限制为 1 个,我已经尝试过,以确保所有解压缩都发生在同一个(高优先级)线程上。查看我的 cpu 显示器,我的四核处理器有大量开销(8 个超线程,我也尝试打开和关闭它)。

如有必要,我很乐意以 root 身份进行内核调整,因为这只是一个研究项目,而不是一个发布应用程序。

有什么建议吗?

TIA

Python 不直接支持设置线程优先级,但有些人幸运地使用 ctypes 做到了这一点。但是,由于 GIL 的工作方式,这可能不会给你最好的结果。

最好的办法可能是使用多处理将 UDP 线程置于单独的进程中,并使用队列将视频从该进程传输到主进程。

为避免死锁,您应该在启动任何线程之前启动 UDP 进程(在启动线程后启动进程 运行 是有问题的,因为线程和 IPC 状态未正确复制到子进程),然后,启动 UDP 进程后,您应该先 lower the priority 主进程,然后再启动其中的任何线程。

Here is an interesting paper 这不是直接切中要点,但提供了一些关于 Python(不幸的是 3)和线程优先级的有用信息。