多线程--GIL锁

GIL

即全局解释器锁,是一个互斥锁,防止多个线程在同一时间执行python代码,因为在一个python进程中,不仅有主线程而且还有该主线程开启的子线程,还有解释器开启的垃圾回收机等解释器级别的线程。因为所有的代码都是共享的,所以垃圾回收线程也可能同时访问到解释器的代码去执行,所以解决这一问题的方法就是GIL,以保证python解释器同一时间只能执行同一个任务的代码。

GIL带来的问题

执行一个py文件,分为三个步骤:

  1. 从硬盘加载python解释器到内存
  2. 从硬盘加载py文件到内存
  3. 解释器解析py文件内容,交给CPU执行

需要明确的是每当执行一个py文件,就会立即启动一个python解释器

GIL是CPython解释器独有的,它将线程的并行变为串行,导致效率降低。

GIL对于性能的影响

  1. 单核下无论IO密集型还是计算密集型GIL都不会产生任何影响
  2. 多核下对于IO密集型任务,GIL会有细微影响,基本可以忽略
  3. CPython中IO密集型任务应该采用多线程,计算密集型应该采用多进程

另外:之所以广泛采用CPython解释器,就是因为大量的应用程序都是IO密集型的,还有另一个很重要的原因是CPython可以无缝对接各种C语言实现的库,这对于一些数学计算相关的应用程序而言非常的happy,直接就能使用各种现成的算法

自定义的线程锁和GIL的区别

GIL保护的是解释器级别的数据安全,比如对象的引用计数,垃圾回收机制等

对于程序中自己定义的数据则没有任何保护效果

from threading import Thread,Lock
import time

a = 0
def task():
    global a
    temp = a
    time.sleep(0.01) 
    a = temp + 1

t1 = Thread(target=task)
t2 = Thread(target=task)
t1.start()
t2.start()
t1.join()
t2.join()
print(a)
#1

过程分析:

1.线程1获得CPU执行权,并获取GIL锁执行代码 ,得到a的值为0后进入睡眠,释放CPU并释放GIL

2.线程2获得CPU执行权,并获取GIL锁执行代码 ,得到a的值为0后进入睡眠,释放CPU并释放GIL

3.线程1睡醒后获得CPU执行权,并获取GIL执行代码 ,将temp的值0+1后赋给a,执行完毕释放CPU并释放GIL

4.线程2睡醒后获得CPU执行权,并获取GIL执行代码 ,将temp的值0+1后赋给a,执行完毕释放CPU并释放GIL,最后a的值也就是1

之所以出现问题是因为两个线程在并发的执行同一段代码,解决方案就是加锁!

from threading import Thread,Lock
import time

lock = Lock()
a = 0
def task():
    global a
    lock.acquire()
    temp = a
    time.sleep(0.01)
    a = temp + 1
    lock.release()

t1 = Thread(target=task)
t2 = Thread(target=task)
t1.start()
t2.start()
t1.join()
t2.join()
print(a)
#2

过程分析:

1.线程1获得CPU执行权,并获取GIL锁执行代码 ,得到a的值为0后进入睡眠,释放CPU并释放GIL,不释放lock

2.线程2获得CPU执行权,并获取GIL锁,尝试获取lock失败,无法执行,释放CPU并释放GIL

3.线程1睡醒后获得CPU执行权,并获取GIL继续执行代码 ,将temp的值0+1后赋给a,执行完毕释放CPU释放GIL,释放lock,此时a的值为1

4.线程2获得CPU执行权,获取GIL锁,尝试获取lock成功,执行代码,得到a的值为1后进入睡眠,释放CPU并释放GIL,不释放lock

5.线程2睡醒后获得CPU执行权,获取GIL继续执行代码 ,将temp的值1+1后赋给a,执行完毕释放CPU释放GIL,释放lock,此时a的值为2

上一篇:并发编程之GIL


下一篇:如何在Cython中调用多线程C函数?