问题描述
更新:为节省您的时间,我在此处直接给出答案。如果您使用纯Python编写代码,则Python 不能同时使用多个cpu内核。但是Python 可以在调用某些用C编写的函数或程序包时同时使用多核,例如Numpy等。
我听说“由于GIL, Python中的多线程不是真正的多线程”。而且我还听说过“ Python多线程可以处理IO密集型任务,而不是计算密集型任务,因为同时只能运行一个线程”。
但是我的经历让我重新考虑了这个问题。我的经验表明,即使对于计算量很大的任务,python多线程 几乎可以加速计算。 (在使用多线程之前,运行以下程序花了我300秒钟,而在使用多线程之后,我花了100秒钟。)
下图显示python
使用cpython
作为包threading
的编译器创建了5个线程,并且所有cpu cores
都占100%的百分比。
我认为屏幕截图可以证明5个cpu内核同时运行。
那么有人可以给我解释吗?我可以将多线程应用于python中的计算密集型任务吗?还是可以在python中同时运行多个线程/内核?
我的代码:
import threading
import time
import numpy as np
from scipy import interpolate
number_list = list(range(10))
def image_interpolation():
while True:
number = None
with threading.Lock():
if len(number_list):
number = number_list.pop()
if number is not None:
# Make a fake image - you can use yours.
image = np.ones((20000,20000))
# Make your orig array (skipping the extra dimensions).
orig = np.random.rand(12800,16000)
# Make its coordinates; x is horizontal.
x = np.linspace(0,image.shape[1],orig.shape[1])
y = np.linspace(0,image.shape[0],orig.shape[0])
# Make the interpolator function.
f = interpolate.interp2d(x,y,orig,kind='linear')
else:
return 1
workers=5
thd_list = []
t1 = time.time()
for i in range(workers):
thd = threading.Thread(target=image_interpolation)
thd.start()
thd_list.append(thd)
for thd in thd_list:
thd.join()
t2 = time.time()
print("total time cost with multithreading: " + str(t2-t1))
number_list = list(range(10))
for i in range(10):
image_interpolation()
t3 = time.time()
print("total time cost without multithreading: " + str(t3-t2))
输出为:
total time cost with multithreading: 112.71922039985657
total time cost without multithreading: 328.45561170578003
top
多线程期间的屏幕截图
top -H
多线程期间的屏幕截图
解决方法
正如您所提到的,Python具有一个“全局解释器锁”(GIL),可以防止两个 Python代码线程同时运行。多线程可以加快IO绑定任务的原因是,例如,在侦听网络套接字或等待磁盘读取时,Python会释放GIL。因此,GIL不会阻止您的计算机同时完成两项工作,而是阻止了同一Python进程中的两个Python线程同时运行。
在您的示例中,您使用numpy和scipy。这些主要由C编写,并利用C / Fortran / Assembly编写的库(BLAS,LAPACK等)。在numpy数组上执行操作时,类似于在该the GIL is released中监听套接字。当释放GIL并调用numpy数组操作时,numpy可以决定如何执行工作。如果需要,它可以产生其他线程或进程,并且它调用的BLAS子例程可能会产生其他线程。如果您要从源代码编译numpy,则可以在构建时配置是否/如何完成配置。
因此,总而言之,您已经找到该规则的例外。如果您仅使用纯Python函数重复进行实验,则会得到完全不同的结果(例如,请参见上面链接到页面的“比较”部分)。
,Python线程是一个真正的线程,只是解释器中一次不能有两个线程(这就是GIL的目的)。该代码的本机部分可以很好地并行运行,而不会在多个线程上争用,只有在返回解释器时,它们才必须在彼此之间进行序列化。
仅将所有CPU内核都加载到100%的事实并不能证明您在“高效”地使用计算机。您需要确保CPU使用率不是由于上下文切换引起的。
如果切换到多处理而不是线程处理(它们非常相似),则不必双重猜测,但是在线程之间传递时,必须封送有效负载。
所以无论如何都要测量一切。