一.介绍
''' 定义: In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.) ''' 结论:在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势
ps:python解释器有很多种 最常见的就是Cpython解释器
GIL本质也是一把互斥锁:将并发变成串行牺牲效率保证数据的安全
用来阻止同一个进程下的多个线程的同时执行(同一个进程内多个线程无法实现并行但是可以实现并发)
GIL的存在是因为CPython解释器的内存管理不是线程安全的
二.GIL介绍
GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
可以肯定的一点是:保护不同的数据的安全,就应该加不同的锁。
要想了解GIL,首先确定一点:每次执行python程序,都会产生一个独立的进程。例如python test.py,python aaa.py,python bbb.py会产生3个不同的python进程
''' #验证python test.py只会产生一个进程 #test.py内容 import os,time print(os.getpid()) time.sleep(1000) ''' python3 test.py #在windows下 tasklist |findstr python #在linux下 ps aux |grep python 验证python test.py只会产生一个进程
在一个python的进程内,不仅有test.py的主线程或者由该主线程开启的其他线程,还有解释器开启的垃圾回收等解释器级别的线程,总之,所有线程都运行在这一个进程内,毫无疑问
#1 所有数据都是共享的,这其中,代码作为一种数据也是被所有线程共享的(test.py的所有代码以及Cpython解释器的所有代码) 例如:test.py定义一个函数work(代码内容如下图),在进程内所有线程都能访问到work的代码,于是我们可以开启三个线程然后target都指向该代码,能访问到意味着就是可以执行。 #2 所有线程的任务,都需要将任务的代码当做参数传给解释器的代码去执行,即所有的线程要想运行自己的任务,首先需要解决的是能够访问到解释器的代码。
综上:
如果多个线程的target=work,那么执行流程是
多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码去执行
解释器的代码是所有线程共享的,所以垃圾回收线程也可能访问到解释器的代码而去执行,这就导致了一个问题:对于同一个数据100,可能线程1执行x=100的同时,而垃圾回收执行的是回收100的操作,解决这种问题没有什么高明的方法,就是加锁处理,如下图的GIL,保证python解释器同一时间只能执行一个任务的代码
三.研究python中的多线程是否有用
四个任务 计算密集型的 10s
单核情况下
开线程更省资源
多核情况下
开进程 10s
开线程 40s
四个任务 IO密集型的
单核情况下
开线程更节省资源
多核情况下
开线程更节省资源
#计算密集型 from multiprocessing import Process from threading import Thread import os,time def work(): res=0 for i in range(100000000): res*=i if __name__ == '__main__': l=[] print(os.cpu_count()) # 本机为8核 start=time.time() for i in range(6): p=Process(target=work) #耗时 7.551327705383301 # p=Thread(target=work) #耗时 27.445183515548706 l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
from multiprocessing import Process from threading import Thread import threading import os,time def work(): time.sleep(2) if __name__ == '__main__': l=[] print(os.cpu_count()) #本机为8核 start=time.time() for i in range(4000): p=Process(target=work) #耗时9.001083612442017s多,大部分时间耗费在创建进程上 # p=Thread(target=work) #耗时2.051966667175293s多 l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
四.GIL和普通互斥锁
from threading import Thread import time n = 100 def task(): global n tmp = n # time.sleep(1) n = tmp -1 t_list = [] for i in range(100): t = Thread(target=task) t.start() t_list.append(t) for t in t_list: t.join() print(n)
1.当程序中的time.sleep(1)开启后,代码执行到这的时候会进入阻塞态,这时候GIL锁会被释放,别的线程可以进行抢锁,在睡的这1秒的时间中,开启的100个线程都可以轮一遍,都可以抢到锁,所以每个线程中拿到的tmp都为100,在执行玩睡眠时间后执行后面的代码,所以结果为99
2.当未开启time.sleep(1)时,开启100个线程,每次只有一个线程可以拿到GIL锁,拿到的线程可以对数据进行操作,别的进程只能等到其释放锁后才能进行抢锁,之后再去执行代码,所以结果为0
五.死锁
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁
from threading import Thread,Lock import time mutexA=Lock() mutexB=Lock() class MyThread(Thread): def run(self): self.func1() self.func2() def func1(self): mutexA.acquire() print('