python多线程_一文读懂python多线程

python多线程_一文读懂python多线程_第1张图片

讲解 python 多线程的文章有很多,但很多都解释的不清不楚,甚至有的文章还抛出 python 是伪多线程的观点。那 python 到底有没有多线程的能力呢?

python 中存在着全局解释锁(GIL),这也是很多文章重复了很多遍。GIL 限制了 python 同一时间只能有一条线程在跑。如果是这样,那些计算密集型的项目,比如 Opencv, TensorFlow 又是怎么利用 python 做并行计算呢?这里我抛出了两个问题:

1、python 能不能使用多线程?

2、python 能不能使用多核并发?

对于问题1,我的回答是能。 python 支持多线程,它的 threading 模块底层就是用 pthread 实现的(不信可以去看源码),所以 threading 起的线程就是实打实的线程。虽然可以通过 threading 起多个线程,但是由于 GIL 的存在,同一时间只能有一条线程在执行,其他线程都在阻塞。这里就有个前提条件,这些线程必须是 python 线程,如果不是则不受 GIL 的控制。那什么是 python 线程呢?简单来说被 GIL 锁住的线程,就是 python 线程,当某一线程释放了 GIL,那么它就不再是 python 线程,想怎么执行就怎么执行。

那 python 线程和非 python 线程有什么区别呢?区别在于 python 线程可以调用 python 的 API,比如 Py_INCREF, PyArg_ParseTupleAndKeywords 等开头为 Py 的接口函数。而非 python 线程不能调用 python 的 API,但好处是不受 GIL 的影响。

python 和非 python线程之间是可以自由切换的,python 线程调用 PyEval_SaveThread 就可以切换为非 python 线程,相反地调用 PyEval_RestoreThread 就切换回 python 线程。在浏览 python 关于 GIL 的 API 时,你可能还会注意到另外一对函数 PyGILState_Ensure 和 PyGILState_Release,这两个函数又是干嘛的呢?简单来讲,当线程不知道自己是 python 还是非 python 线程,但又要调用 python 的 API 时,就需要 ensure 一下,获取 GIL,确保自己是 python 线程,调用完后再用 PyGILState_Release 释放掉。

对于问题2,python 是能使用多核并发执行,由以一个问题的答案可知,把需要并行执行的任务用非 python 线程执行就可以了。

下面我们用一段代码来验证以上观点。

// test_extend.cc

上面这段代码主要实现了两个 N*N 的矩阵的乘法,并提供了 python 接口。这是一段很笨的矩阵乘法代码,性能非常差,但是不要紧,我们的目的是把 CPU 跑满。

下面是CMake工程。

CMakeLists.txt

project

将 CMakeLists.txt 和c文件放在同一目录,执行 cmake . && make

得到一个名字为 Extend.cpython-37m-x86_64-linux-gnu.so 库文件。如果想在 python 里面引入这个库,只需要加入代码 import Extend 就可以了。

下面是测试代码:

(test.py)

import 

当你跑起来,使用 htop 或者 top 查看进程的 cpu 占用率,在多核机器上(大于或等于3核),你会发现 cpu 的占有率为 300%,因为这里起了3条线程,2条子线程和1条主线程。所以 python 支持多核多线程。

当把 PyAllowThreads allowThreads 这行代码注释掉,无论起多少个线程 cpu 占有率永远为100%。

很多高性能计算库都使用了这个机制,numpy 就是其中之一,我们可以测试下面这段代码:

(test_numpy.py)

import threading
import numpy as np

DIM = 30000

def test():
    A = np.ones((DIM, DIM))
    B = np.ones((DIM, DIM))
    C = np.multiply(A, B)

# thread 1
t1 = threading.Thread(target=test)
t1.setDaemon(True)
t1.start()

# thread 2
t2 = threading.Thread(target=test)
t2.setDaemon(True)
t2.start()

# main thread
test()

CPU 占有率同样为300%。

结论:

1、python 支持多线程多核并行执行,只要正确地释放 GIL 就可以了。

2、在做计算密集型任务时,应该使用 numpy 等高性能计算库,它们会把计算都下放到非 python 线程,应避免使用 python 自带的加减乘除。

你可能感兴趣的:(python多线程)