Python全局解释器锁(GIL)在多核系统上使用Linux的taskset来绕过?

28
所以我刚刚看完了关于Python全局解释器锁(GIL)的讲座http://blip.tv/file/2232410
总结一下,对于单核系统来说,GIL是一个相当不错的设计(Python基本上将线程处理/调度交给操作系统)。但是,在多核系统上,这可能会导致IO密集型线程受到CPU密集型线程的严重阻塞,造成上下文切换的开销,Ctrl-C问题[*]等等。
所以,既然GIL限制我们基本上只能在一个CPU上执行Python程序,我的想法是为什么不接受这一点,并简单地使用Linux上的taskset命令将程序的亲和性设置为系统上的某个核心/CPU(尤其是在多核系统上运行多个Python应用程序的情况下)?
所以,我的问题是这样的:有人在Linux上使用过taskset来运行Python应用程序吗(尤其是在Linux系统上运行多个应用程序,以便将多个核心与一个或两个绑定到特定核心的Python应用程序一起使用),如果有的话,结果如何?这样做是否值得?对于某些工作负载,会使情况变得更糟吗?我计划这样做并进行测试(基本上看看程序运行需要更长时间还是更短时间),但我很想听听其他人的经验。
补充说明:David Beazley(在链接视频中发表演讲的那个人)指出,一些C/C++扩展手动释放GIL锁,如果这些扩展针对多核进行了优化(例如科学或数值数据分析等),那么它们将无法享受到多核的好处,而是被限制在单个核心上(从而可能显著减慢程序的运行速度)。另一方面,如果您不使用此类扩展程序。
我不使用多进程模块的原因是(在这种情况下),程序的一部分严重依赖网络I/O(HTTP请求),因此拥有一个工作线程池是提高性能的绝佳方式,因为一个线程发出一个HTTP请求,然后由于等待I/O而释放GIL,另一个线程可以执行其任务,所以程序的这部分可以轻松运行100多个线程而不会对CPU造成太大负担,并且让我真正利用可用的网络带宽。至于无栈Python等,我对重写程序或替换Python堆栈并不过于感兴趣(可用性也是一个问题)。
[*] 只有主线程能接收信号,所以如果发送ctrl-C,Python解释器基本上会尝试让主线程运行以处理信号,但由于它不能直接控制哪个线程运行(这由操作系统决定),它基本上告诉操作系统不断切换线程,直到最终达到主线程(如果你运气不好可能需要一段时间)。
7个回答

11

1
很遗憾,这对我来说不太适用,程序的一部分非常依赖网络I/O(HTTP请求),因此线程是一种非常轻量级的方式,可以从框中挤出大量性能(100多个线程抓取东西,在程序的那部分几乎没有CPU负载,因为我等待远程服务器响应了数百毫秒)。 - Kurt
4
如果您正在使用线程进行IO操作,通常情况下GIL不应该是一个问题。Python IO例程通常是用C编写的,在其操作期间会释放GIL。当多个线程需要在解释器上运行Python字节码时,GIL的问题就会变得很大。 - ynimous
2
此外,如果您正在编写网络应用程序,我建议您查看http://twistedmatrix.com/。在您通过学习曲线之后,它将使您的生活变得更加轻松。 - ynimous
3
有没有什么原因不能使用多进程模块来处理CPU绑定的线程? - ynimous
现在这个应用程序相当简单;因为它使用了线程,我有共享内存和工作队列,这使得处理一切变得非常简单(而且非常高效)。我宁愿不将其拆分并从整个消息传递开始。此外,CPU绑定的问题并不那么严重,将其留在线程中比把它们全部拆开要简单得多。至于Twisted,那对我来说太过头了,超出了我的需求范围。 - Kurt
显示剩余3条评论

9

我从未听说过有人使用taskset来提高Python性能。这并不意味着在你的情况下不能发生,但一定要发布你的结果,以便其他人批判你的基准测试方法并提供验证。

个人建议是使用消息队列将I/O线程与CPU绑定线程解耦。这样,你的前端现在完全是网络I/O限制(有些使用HTTP接口,有些使用消息队列接口),非常适合你的线程情况。然后,CPU密集型进程可以使用多进程或仅作为等待消息队列中的工作到达的单个进程。

从长远来看,你可能还想考虑用Twisted或类似的eventlets替换你的线程I/O前端,因为即使它们不能提高性能,它们应该改善可扩展性。你的后端现在已经具有可扩展性,因为你可以在任意数量的机器+ CPU上运行你的消息队列。


我曾经看到taskset在以下Linux发行版/内核组合中显著提高了Python的性能(20%-50%):1)Lubuntu 12.10 + A8-5600K和2)Slitaz 4.0 + Mobile i3...有趣的是,相同的代码在Lubuntu 12.04 + Mobile i3和Lubuntu 12.04 + AMD Athlon II X2上更快,而且不依赖于taskset。我认为这可能是由于内核调度优化所致。 Lubuntu 12.04在旧架构上没有问题。 Lubuntu 12.10在较新的(HT /共享核心硬件)AMD APU上存在问题。而Slitaz(不太优化的内核)在旧的(HT)Mobile i3上存在问题。 - dhj

3

在Python 3中,可以使用模块multiprocessing将任务分叉到触发每个任务的单独Python解释器的分叉进程中。这不是线程系统,而是一种分叉进程方法的解决方案。请注意这一点。 - Leo Pepe

1
多年来,我发现以下经验法则足以解决问题:如果工作人员依赖于某些共享状态,则对于每个核心使用一个多进程进程(CPU绑定),并为每个核心使用一个固定的工作线程池(I/O绑定)。操作系统将负责将不同的Python进程分配到各个核心。

你可能想观看我链接的视频,由于Python将所有线程工作留给操作系统,因此可能会出现一些非常愚蠢/丑陋的行为。 - user31056
2
我已经这样做了,我的建议是使用多进程将CPU负载分配到处理器上(假设在这些进程之间没有太多信息需要共享),并使用线程来满足I/O需求。 - wr.

1

Python GIL是每个Python解释器的。这意味着在进行多进程时避免出现问题的唯一方法就是启动多个解释器(即使用单独的进程而不是线程进行并发),然后使用其他IPC原语在进程之间进行通信(例如套接字)。话虽如此,当使用阻塞I/O调用的线程时,GIL不是一个问题。

正如前面提到的,GIL的主要问题是您无法同时执行2个不同的Python代码线程。在阻塞I/O调用上阻塞的线程被阻塞,因此不会执行Python代码。这意味着它不会阻塞GIL。如果您有两个CPU密集型任务在单独的Python线程中,则这就是GIL在Python中杀死多处理的地方(仅适用于CPython实现,如前所述)。因为GIL会阻止CPU#1执行Python线程,而CPU#0正在忙于执行另一个Python线程。


1

在Python中GIL被移除之前,协程可以用来代替线程。我有可靠的消息称,这种策略已经被两个成功的初创公司采用,在至少一个案例中使用了greenlets。


1
你可以使用多进程模块,而不是为每个任务创建线程,它会创建另一个解释器进程来执行你的代码。这样可以让你的应用程序充分利用多核系统的优势。我看到这种方法唯一的问题是,通过在内存中创建一个全新的进程栈,会产生相当大的开销。
Python的多进程模块: http://docs.python.org/dev/library/multiprocessing.html "我没有使用多进程模块的原因是(在这种情况下),程序的一部分严重依赖网络I/O(HTTP请求),所以拥有一个工作线程池是提高性能的好方法..."
关于这一点,我想你也可以有一个进程池:http://docs.python.org/dev/library/multiprocessing.html#using-a-pool-of-workers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接