在Linux上使用taskset解决Python全局解释器锁(GIL)在多核系统上的问题?

28 投票
5 回答
32430 浏览
提问于 2025-04-15 12:13

我刚看完一个关于Python全局解释器锁(GIL)的讲座,链接在这里:http://blip.tv/file/2232410

简单来说,GIL在单核系统上设计得还不错(Python基本上把线程的处理和调度交给操作系统)。但是在多核系统上,这个设计可能会出现严重问题,比如IO密集型的线程会被CPU密集型的线程严重阻塞,还有上下文切换的开销,以及ctrl-C的问题[*]等等。

所以,既然GIL限制了我们只能在一个CPU上执行Python程序,我在想,为什么不接受这个限制,直接在Linux上用taskset命令把程序绑定到某个特定的核心/CPU上呢?特别是在多核系统上同时运行多个Python应用的情况下,这样做会不会更好?

所以我最终想问的是:有没有人尝试过在Linux上用taskset来运行Python应用(尤其是在多个应用同时运行的情况下,让多个核心可以被使用,而一两个Python应用绑定到特定核心上)?如果有,结果怎么样?这样做值得吗?对某些工作负载会不会更糟?我打算试试这个方法(基本上就是看看程序运行的时间是变长还是变短),但也很想听听大家的经验。

补充一下:David Beazley(在视频中讲课的那位)提到,一些C/C++扩展会手动释放GIL锁,如果这些扩展是为多核优化的(比如科学计算或数值数据分析等),那么它们在进行大量计算时就无法充分利用多核的优势,反而会被限制在单核上运行(这可能会显著拖慢你的程序)。另一方面,如果你不使用这样的扩展……

我不使用多进程模块的原因是,这个程序的一部分是高度依赖网络I/O的(比如HTTP请求),所以使用一池工作线程是提高性能的好方法,因为一个线程发出HTTP请求后,由于在等待I/O,它就会放弃GIL,另一个线程就可以继续工作。这样程序可以轻松运行100多个线程,而不会对CPU造成太大压力,还能充分利用可用的网络带宽。至于无栈Python等,我并不太想重写程序或更换我的Python环境(可用性也是个问题)。

[*] 只有主线程可以接收信号,所以如果你按下ctrl-C,Python解释器基本上会试图让主线程运行,以便处理这个信号。但由于它并不直接控制哪个线程在运行(这由操作系统来决定),所以它基本上会告诉操作系统不断切换线程,直到最终找到主线程(如果运气不好,可能会花费一些时间)。

5 个回答

1

多年来,我发现一个简单的经验法则很有效:如果工作者需要依赖某些共享的状态,我就会为每个核心(CPU)使用一个多进程的进程(适合处理计算密集型任务),而对于每个核心,我会使用一个固定数量的工作线程(适合处理输入输出密集型任务)。操作系统会负责把不同的Python进程分配到各个核心上。

1

在Python中,直到全局解释器锁(GIL)被去掉之前,可以用协程来代替线程。我听说有两家成功的初创公司已经采用了这种方法,至少有一个案例是使用了绿色线程(greenlets)。

9

我从来没听说过有人用taskset来提高Python的性能。这并不代表在你的情况下不可能,但如果你有结果,最好分享出来,让别人可以评价你的测试方法并提供验证。

就我个人而言,我建议你把输入输出(I/O)线程和需要大量CPU资源的线程分开,使用消息队列。这样,你的前端就完全依赖网络输入输出(I/O),有些是通过HTTP接口,有些是通过消息队列接口,这样更适合你的线程情况。然后,CPU密集型的处理可以使用多进程,或者只是作为独立的进程,等待消息队列中的工作到来。

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

撰写回答