翻译:什么是全局解释器锁GIL? - 知乎 (zhihu.com)
什么是GIL
全局解释锁(Global Interpreter Lock, GIL)是一个互斥锁,它规定Python解释器同一时刻只允许一个线程运行。
GIL的优点
Python 使用引用计数进行内存管理,如果有多个线程参与的竞争条件时(指多个线程同一时间想要修改共享数据),此时我们需要保护引用计数变量以免受竞争条件的影响。如果不对引用计数变量进行保护 ,可能发生内存泄漏(对象所在的内存得不到释放)或者提前释放。这样会造成稀奇古怪的 bug。
如果我们使用锁保护引用计数变量的话,那么所有共享数据的创建和回收都不会发生上面的问题。
但在多个锁的情况下,频繁的获取/释放锁会降低程序的性能。而且如果我们给每个对象都加上锁,那么很可能死锁问题(A等待B,B等待C,C等待A)。
GIL 是解释器层面的一个锁,运行任何一段 Python 字节码的时候,都需要先获得解释器锁,这就使得死锁的情况不可能发生(因为只有一个锁)。
GIL的缺点
GIL 对单线程程序丝毫没有影响,但它会造成 CPU 密集型/多线程程序中的瓶颈。
CPU 密集型程序指的是极致利用 CPU 性能。比如像矩阵乘法,图片处理这样的数学计算。
IO 密集型程序指的花费大量时间消耗在 输入/输出 操作,比如文件读写,网络数据传输/接收。造成这些是因为 CPU 和速度,内存的速度,硬盘等外设的速度完全不在一个数量级,很多时候程序需要等待它们所需的资源到位才能接着运行。
举一个CPU密集型程序的例子,先使用单线程执行:
import time
from threading import Thread
COUNT = 50000000
def countdown(n):
while n>0:
n -= 1
start = time.time()
countdown(COUNT)
end = time.time()
print('Time taken in seconds -', end - start)
输出Time taken in seconds - 6.20024037361145
换成两个线程执行:
import time
from threading import Thread
COUNT = 50000000
def countdown(n):
while n>0:
n -= 1
t1 = Thread(target=countdown, args=(COUNT//2,))
t2 = Thread(target=countdown, args=(COUNT//2,))
start = time.time()
t1.start()
t2.start()
t1.join()
t2.join()
end = time.time()
print('Time taken in seconds -', end - start)
输出Time taken in seconds - 6.924342632293701
可以看出单线程和多线程的效率相差无几,在多线程的版本中,由于GIL的存在,实际还是以单线程状态轮流执行两个线程
解决GIL影响多线程效率的问题
既然多线程会收GIL影响,那可以使用多进程,每个进程都有自己的解释器和内存空间
Python中的multiprocessing
库可以让程序以多进程执行:
from multiprocessing import Pool
import time
COUNT = 50000000
def countdown(n):
while n>0:
n -= 1
if __name__ == '__main__':
pool = Pool(processes=2)
start = time.time()
r1 = pool.apply_async(countdown, [COUNT//2])
r2 = pool.apply_async(countdown, [COUNT//2])
pool.close()
pool.join()
end = time.time()
print('Time taken in seconds -', end - start)
输出:Time taken in seconds - 4.060242414474487
执行效率对比单线程和多线程有了明显提升
网友评论