我可以在 python 中为计算密集型任务应用多线程吗?

Can I apply multithreading for computationally intensive task in python?

更新:为了节省大家的时间,我这里直接给出答案。 Python 如果您使用纯 Python 编写代码,则不能 同时使用多个 cpu 内核。但是Python可以同时调用一些用C写的函数或者包,比如Numpy等


我听说“python 中的多线程不是真正的多线程,因为 GIL”。而且我还听说“python 多线程可以处理 IO 密集型任务而不是计算密集型任务,因为同时只有一个线程 运行ning".

但我的经历让我重新思考这个问题。我的经验表明,即使对于计算密集型任务,python 多线程 也可以 几乎加速计算。 (多线程之前,运行下面的程序耗时300秒,使用多线程后,耗时100秒。)

下图显示python使用CPython作为编译器包threading创建了5个线程,所有cpu cores都接近100%。

我觉得截图可以证明5个cpu核同时运行ning

所以谁能给我解释一下?我可以在 python 中为计算密集型任务应用多线程吗?或者可以在python中同时使用多个threads/cores运行?

我的代码:


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多线程期间的屏幕截图

top 的屏幕截图,然后按 1 多线程期间

top -H多线程的截图

Python线程是真正的线程,只是解释器中不能同时有两个线程(这就是 GIL 的意义所在)。代码的本机部分可以很好地 运行 并行而不会在多个线程上争用,只有当回到解释器时,它们才必须相互序列化。

您将所有 CPU 个核心加载到 100% 的事实并不能证明您正在“高效”地使用机器。您需要确保 CPU 用法不是由于上下文切换引起的。

如果您切换到多处理而不是线程(它们非常相似),您将不必反复猜测,但在线程之间传递时您将不得不编组有效负载。

所以无论如何都需要测量。

正如您提到的,Python 有一个“全局解释器锁”(GIL),可以防止 Python 代码 运行 的两个线程同时宁。 multi-threading 可以加速 IO 绑定任务的原因是 Python 在例如监听网络套接字或等待磁盘读取时释放 GIL。因此 GIL 不会阻止您的计算机同时完成两项工作,它会阻止同一 Python 进程中的两个 Python 线程同时 运行。

在您的示例中,您使用了 numpy 和 scipy。这些主要是用 C 编写的,并利用了用 C/Fortran/Assembly 编写的库(BLAS、LAPACK 等)。当您对 numpy 数组执行操作时,它类似于在 the GIL is released 中监听套接字。当 GIL 被释放并调用 numpy 数组操作时,numpy 开始决定如何执行工作。如果需要,它可以生成其他线程或进程,并且它调用的 BLAS 子例程可能会生成其他线程。准确地说 if/how 如果您想从源代码编译 numpy,可以在构建时配置完成。

总而言之,您发现了规则的例外情况。如果您仅使用纯 Python 函数重复实验,您会得到截然不同的结果(例如,请参阅上面链接的页面的“比较”部分)。