Python中的多个进程和线程
我听说过这样一句话:“如果你想让并行应用程序达到最佳性能,你应该创建和你电脑的CPU数量一样多的进程,然后在每个进程中再创建一些(多少个呢?)线程。”
这是真的吗?
我写了一段代码来实现这个想法:
import multiprocessing, threading
number_of_processes = multiprocessing.cpu_count()
number_of_threads_in_process = 25 # some constant
def one_thread():
# very heavyweight function with lots of CPU/IO/network usage
do_main_work()
def one_process():
for _ in range(number_of_threads_in_process):
t = threading.Thread(target=one_thread, args=())
t.start()
for _ in range(number_of_processes):
p = multiprocessing.Process(target=one_process, args=())
p.start()
这样做对吗?我的 do_main_work
函数真的能并行运行,而不会遇到任何GIL的问题吗?
谢谢。
3 个回答
我觉得你每个进程使用的线程数量有点多。一般来说,对于任何英特尔处理器,每个进程的线程数是2。处理器的核心数量从2个(比如英特尔的i3)到6个(比如英特尔的i7)不等。所以当所有进程同时运行时,最多的线程数量就是6个核心乘以2,也就是12个线程。
# very heavyweight function with lots of CPU/IO/network usage
很多CPU会因为GIL而受到影响,所以你只有通过多个进程才能获得好处。
IO和网络(其实网络也是一种IO)不会受到GIL太大的影响,因为在IO操作完成后,锁会被明确释放,然后再重新获取。在CPython中有相关的宏定义:
Py_BEGIN_ALLOW_THREADS
... Do some blocking I/O operation ...
Py_END_ALLOW_THREADS
由于在包装代码中使用了GIL,性能还是会受到一些影响,但使用多个线程的性能仍然会更好。
最后——这是一个通用的规则——不仅仅适用于Python:最佳的线程/进程数量取决于程序实际在做什么。一般来说,如果程序对CPU的使用非常密集,当进程数量超过CPU核心数时,几乎不会有性能提升。例如,Gentoo的文档提到,编译器的最佳线程数是CPU核心数加1。
这其实很大程度上取决于你在做什么。
要记住,在CPython中,一次只能有一个线程在执行Python字节码(这是因为有个叫GIL的东西)。所以如果你在CPython中处理计算密集型的问题,线程的帮助可能不大。
一种可以让工作并行处理的方法是使用multiprocessing.Pool
。默认情况下,这个池不会使用超过你CPU核心数量的进程。如果使用的进程多于CPU核心,反而会让它们争抢资源(比如CPU和内存),这样反而没什么用。
不过,要充分利用多个处理器,你得有足够的工作让它们去做!换句话说,如果问题不能被分解成可以单独计算的小块,并且这些小块可以并行处理,那么很多CPU核心也没什么用。
另外,并不是所有的问题都是计算量大的。
计算机的内存(RAM)比CPU慢得多。如果你处理的数据集比CPU的缓存大得多,从内存中读取数据和返回结果可能会成为速度的瓶颈。这种情况被称为内存限制。
如果你处理的数据量远远超过机器的内存,你的程序就会频繁地从硬盘读取和写入数据。相比于内存,硬盘的速度很慢,而相比于CPU,硬盘就更慢了,所以你的程序会变得I/O限制。