Python全局解释锁

翻译:什么是全局解释器锁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
执行效率对比单线程和多线程有了明显提升

你可能感兴趣的:(Python全局解释锁)