gpt4 book ai didi

python - GIL 正在杀死 I/O 绑定(bind)线程

转载 作者:太空宇宙 更新时间:2023-11-03 16:45:12 25 4
gpt4 key购买 nike

我有一个主要用 Python 编写的网站。处理 Python 绑定(bind)请求的 Python 进程有一个调度线程,它从 Web 服务器获取请求,然后将它们简单地调度到线程池进行处理。因此,调度线程中完成的工作非常简单;它只是通过 Unix 套接字读取请求并在线程池上进行一些同步。正常情况下每秒可以调度2000多个请求。

然而,有时会发生一些奇怪的事情。该网站的一部分对上传的文件进行一些图像处理,由于图像处理算法完全用 Python 编写,因此需要一点时间,在 CPU 上旋转。对于较大的图像,可能需要 5 秒或更长时间。不过,这本身就很好;奇怪的是,当它进行处理时,调度线程的吞吐量急剧下降。当图像处理器运行时,调度吞吐量下降到每秒大约 20-30 个请求 - 几乎两个数量级!

这给我带来了一些小麻烦,因为在繁忙时间,Python 处理程序每​​秒收到大约 50-100 个请求,因此无法跟上。对于需要大约 3 秒或更长时间的图像处理请求,缓冲区开始填满,因此 Web 服务器被迫开始丢弃绑定(bind)到 Python 的请求。

Clip of visualization

我编写了一个可视化工具来帮助调试问题,并且 this image (如上图所示)演示了正在发生的情况。每个请求的调度沿 X 轴绘制为一条线,每个后续请求绘制在后续 Y 坐标上。每条垂直网格线代表一秒,红色网格线是我的 HTTP 服务器记录它开始丢弃请求的位置。可以清楚地看到,在此之前大约 2.5 秒,调度速度减慢了很多,与访问日志相比,这就是图像处理器启动的地方。

我的假设是,这是因为受 CPU 限制的图像处理器线程正在占用 GIL,并且调度程序必须等待某个特定的“处理窗口”完成,直到受 CPU 限制的线程自愿释放 GIL 以用于其他任务。要运行的线程。而调度程序线程则在每次进入阻塞系统调用时释放 GIL,然后必须等待另一个整个处理窗口完成才能处理下一个请求。

如果这个假设是正确的,那么我意识到我可以通过 fork 一个单独的进程来完成图像处理工作来解决这个问题。然而,这会使代码变得复杂并使其变得更难看,所以我想尽可能避免这种情况。

因此:有什么方法可以避免这个明显的 GIL 问题吗?我可以让调度程序线程不会那么容易地放弃 GIL,从而允许它处理处理窗口之间的一些积压吗? GIL CPU 窗口可以“调整”,或者我可以为 CPU 绑定(bind)线程或类似的东西分配一些较低的“GIL 优先级”吗?还有其他办法吗?或者我可能完全误解了这个问题?

抱歉啰嗦了,但我实在想不出更简洁的方式来描述这种情况。

最佳答案

我确实设法弄清楚为什么会发生这种情况。事实证明,阻塞系统调用本身并不是问题,而是线程池实现的一部分使调度线程等待,直到工作线程确认它已接受请求(出于会计原因)基本上)通过发出调度线程等待的条件变量的信号。

我尝试重新实现线程池,以便调度线程可以简单地发布请求,而不必与工作线程同步工作,这似乎使问题完全消失。现在,可视化一段图像处理期间的请求分派(dispatch)并没有显示出任何放缓。据推测,两个线程之间 GIL 的切换为第三个受 CPU 限制的线程创造了一个更大的窗口,可以在更长的时间内抢占它。

我想,要吸取的教训是,当前的 CPython(我在运行它的服务器上使用 3.4.2)似乎可以很好地混合 I/O 绑定(bind)和 CPU 绑定(bind)线程,但是两个或多个彼此同步工作的线程可能会因 CPU 限制线程而陷入饥饿状态。

关于python - GIL 正在杀死 I/O 绑定(bind)线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36378201/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com