6.1 线程
6.1.1 多线程适用于阻塞式IO场景,不适用于并行计算场景
Python
的标准实现是CPython
。
CPython
执行Python
代码分为2个步骤:首先,将文本源码解释编译为字节码,然后再用一个解释器去解释运行字节码。字节码解释器是有状态的,需要维护该状态的一致性,因此使用了GIL
(Global Interpreter Lock
,全局解释器锁)。
GIL
的存在,使得CPython
在执行多线程代码的时候,同一时刻只有一个线程在运行,无法利用多CPU
提高运算效率。但是这个特点也带来了一个好处:CPython
运行多线程的时候,内部对象缺省就是线程安全的。这个特性,被非常多的Python库开发者所依赖,直到CPython的开发者想要去除GIL的时候,发现已经有大量的代码库重度依赖
这个GIL
带来的内部对象缺省就是线程安全的特性,变成一个无法解决的问题了。
虽然多线程在并行计算场景下无法带来好处,但是在阻塞式IO
场景下,却仍然可以起到提高效率的作用。这是因为阻塞式IO
场景下,线程在执行IO
操作时并不需要占用CPU
时间,此时阻塞IO
的线程可以被挂起的同时继续执行IO
操作,而让出CPU时间给其他线程执行非IO
操作。这样一来,多线程并行IO
操作就可以起到提高运行效率的作用了。
综上,Python
的标准实现CPython
,由于GIL
的存在,同一个时刻只能运行一个线程,无法充分利用多CPU
提升运算效率,因此Python
的多线程适用于阻塞式IO
的场景,不适用于并行计算的场景。
下面举一个对计算量有要求的求一个数的因数分解的代码实例,来说明Python
多线程不适用于并行计算的场景:
# -*- coding:utf-8 -*-
from time import time
from threading import Thread
def factorize(number):
for i in range(1, number + 1):
if number % i == 0:
yield i
class FactorizeThread(Thread):
def __init__(self, number):
Thread.__init__(self)
self.number = number
def run(self):
self.factors = list(factorize(self.number))
def test(numbers):
start = time()
for number in numbers:
list(factorize(number))
end = time()
print('Took %.3f seconds' % (end - start))
def test_thread(numbers):
start = time()
threads = []
for number in numbers:
thread = FactorizeThread(number)
thread.start()
threads.append(thread)
for t in threads: