27

所以我刚刚看完了关于 Python Global Interpreter Lock (GIL) http://blip.tv/file/2232410的演讲。

它的要点是 GIL 对于单核系统来说是一个非常好的设计(Python 基本上将线程处理/调度留给了操作系统)。但这在多核系统上可能会严重适得其反,最终导致 IO 密集型线程被 CPU 密集型线程严重阻塞、上下文切换的代价、ctrl-C 问题 [*] 等等。

因此,由于 GIL 限制我们基本上只能在一个 CPU 上执行 Python 程序,我的想法是为什么不接受这一点并简单地使用 Linux 上的任务集来将程序的亲和性设置为系统上的某个核心/cpu(尤其是在在多核系统上运行多个 Python 应用程序)?

所以最终我的问题是:有没有人尝试过在 Linux 上使用带有 Python 应用程序的任务集(尤其是在 Linux 系统上运行多个应用程序时,以便可以将多个内核与绑定到特定内核的一个或两个 Python 应用程序一起使用),如果是这样的话结果是什么?值得吗?对于某些工作负载,它会使情况变得更糟吗?我计划这样做并对其进行测试(基本上是看看程序是否需要更多或更少的时间来运行),但我很想听听其他人的经验。

补充:David Beazley(在链接视频中发表演讲的人)指出,一些 C/C++ 扩展手动释放 GIL 锁,如果这些扩展针对多核(即科学或数字数据分析等)进行了优化,那么而不是获得多核的好处来处理数字,扩展将被有效地削弱,因为它仅限于单核(因此可能会显着减慢您的程序速度)。另一方面,如果您不使用这样的扩展

我不使用多处理模块的原因是(在这种情况下)程序的一部分是严重的网络 I/O 绑定(HTTP 请求),因此拥有一个工作线程池是一种很好的方式来提高性能,因为一个线程触发一个 HTTP 请求,然后因为它在等待 I/O 放弃了 GIL,另一个线程可以做到这一点,所以程序的一部分可以轻松运行 100 多个线程而不会对 CPU 造成太大伤害,让我实际使用可用的网络带宽。至于无堆栈 Python/etc,我对重写程序或替换我的 Python 堆栈并不太感兴趣(可用性也是一个问题)。

[*] 只有主线程可以接收信号,所以如果你发送一个 ctrl-C,Python 解释器基本上会尝试让主线程运行以便它可以处理信号,但是因为它不直接控制运行哪个线程(这留给操作系统)它基本上告诉操作系统继续切换线程,直到它最终到达主线程(如果你不走运可能需要一段时间)。

4

7 回答 7

10

另一个解决方案是: http ://docs.python.org/library/multiprocessing.html

注 1:这不是Python 语言的限制,而是 CPython 实现的限制。

注意 2:关于亲和力,您的操作系统本身不应该有问题。

于 2009-06-13T06:49:25.750 回答
7

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

不过就个人而言,我会使用消息队列将您的 I/O 线程与 CPU 绑定线程分离。这样,您的前端现在完全受网络 I/O 限制(一些带有 HTTP 接口,一些带有消息队列接口)并且非常适合您的线程情况。然后,CPU 密集型进程可以使用多处理,也可以只是等待工作到达消息队列的单个进程。

从长远来看,您可能还想考虑用 Twisted 或类似eventlet的东西替换您的线程 I/O 前端,因为即使它们不会提高性能,它们也应该提高可伸缩性。您的后端现在已经是可扩展的,因为您可以根据需要在任意数量的机器+cpu 上运行您的消息队列。

于 2009-06-13T09:51:55.297 回答
3

一个有趣的解决方案是 Ryan Kelly 在他的博客上报告的实验:http ://www.rfk.id.au/blog/entry/a-gil-adventure-threading2/

结果似乎非常令人满意。

于 2011-11-20T20:51:04.953 回答
1

多年来,我发现以下经验法则就足够了:如果工作人员依赖于某些共享状态,我会为每个核心使用一个多处理进程(CPU 限制),并且每个核心使用一个固定的工作线程池(I/O 限制) )。操作系统将负责将不同的 Python 进程分配给内核。

于 2009-06-13T08:37:38.080 回答
1

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

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

于 2009-06-13T19:21:22.337 回答
1

在从 Python 中删除 GIL 之前,可以使用协程代替线程。我有充分的权威表明,这一策略已由两家成功的初创企业实施,至少在一个案例中使用了 greenlets。

于 2009-06-17T07:43:33.697 回答
1

这是一个相当古老的问题,但是因为每次我搜索与 python 和多核系统上的性能相关的信息时,这篇文章总是在结果列表中,我不会让这件事过去,也不会分享我的想法。

您可以使用多处理模块,而不是为每个任务创建线程,它会创建另一个 cpython 编译器进程来解释您的代码。它将使您的应用程序能够利用多核系统。我在这种方法中看到的唯一问题是,在内存上创建一个全新的进程堆栈会产生相当大的开销。(http://en.wikipedia.org/wiki/Thread_(computing)#How_threads_differ_from_processes

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

阿特,狮子座

于 2012-01-19T13:10:17.537 回答