二 GIL介绍
GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
可以肯定的一点是:保护不同的数据的安全,就应该加不同的锁。
在一个python的进程内,不仅有test.py的主线程或者由该主线程开启的其他线程,还有解释器开启的垃圾回收等解释器级别的线程,总之,
所有线程都运行在这一个进程内,毫无疑问
#1 所有数据都是共享的,这其中,代码作为一种数据也是被所有线程共享的(test.py的所有代码以及Cpython解释器的所有代码)
例如:test.py定义一个函数work(代码内容如下图),在进程内所有线程都能访问到work的代码,于是我们可以开启三个线程
然后target都指向该代码,能访问到意味着就是可以执行。
#2 所有线程的任务,都需要将任务的代码当做参数传给解释器的代码去执行,即所有的线程要想运行自己的任务,
首先需要解决的是能够访问到解释器的代码。
综上:
如果多个线程的target=work,那么执行流程是
多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码去执行
解释器的代码是所有线程共享的,所以垃圾回收线程也可能访问到解释器的代码而去执行,这就导致了一个问题:对于同一个数据100,
可能线程1执行x=100的同时,而垃圾回收执行的是回收100的操作,解决这种问题没有什么高明的方法,就是加锁处理,
如下图的GIL,保证python解释器同一时间只能执行一个任务的代码
三 GIL与Lock
GIL保护的是解释器级的数据,保护用户自己的数据则需要自己加锁处理,如下图
四 GIL与多线程
有了GIL的存在,同一时刻同一进程中只有一个线程被执行
在多线程中,多个线程并发的去执行时,会都去抢 GIL锁 抢到锁的会在解释器中执行线程代码,当抢到的线程执行结束后,其他的线程还在外边等着抢,抢到的就在子线程中执行代码
结论:
对计算来说,cpu越多越好,但是对于I/O来说,再多的cpu也没用
当然对运行一个程序来说,随着cpu的增多执行效率肯定会有所提高(不管提高幅度多大,总会有所提高),这是因为一个程序基本上不会是纯计算或者纯I/O,所以我们只能相对的去看一个程序到底是计算密集型还是I/O密集型,从而进一步分析python的多线程到底有无用武之地
总结:
1.每个cpython 里边都有一个GIL
2.GIL导致统一进程内多个线程同一时间内只能有一个运行
3.之所以有GIL 是因为CPython的线程管理不是线程安全的
4.对于计算密集的多用多进程,对于I/O密集型的多用多线程
五 多线程性能测试
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()) #本机为4核 start=time.time() for i in range(4): p=Process(target=work) #耗时5s多 p=Thread(target=work) #耗时18s多 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) print('===>') if __name__ == '__main__': l=[] print(os.cpu_count()) #本机为4核 start=time.time() for i in range(400): # p=Process(target=work) #耗时12s多,大部分时间耗费在创建进程上 p=Thread(target=work) #耗时2s多 l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start)) I/O密集型:多线程效率高
应用:
1多线程用于IO密集型,如socket,爬虫,web
2多进程用于计算密集型,如金融分析
Rlock 递归锁
from threading import Thread,Lock,RLock import time # mutexA=Lock() #用互斥锁就会锁死,因为两个函数互相拿着对方想要的锁, # 但是想要放出自己不想要的锁的前提就是要拿到想要的锁然后程序就在那里一直循环的卡在哪儿 # mutexB=Lock() mutexA=mutexB=RLock() #递归锁,与互斥锁的区别就是可以无限的acquire class MyThread(Thread): def run(self): self.f1() self.f2() def f1(self): mutexA.acquire() print("%s拿到了A锁"%self.name) mutexB.acquire() print('%s拿到了B锁'%self.name) mutexB.release() mutexA.release() def f2(self): mutexB.acquire() print("%s拿到了B锁"%self.name) time.sleep(0.1) mutexA.acquire() print('%s拿到了A锁'%self.name) mutexA.release() mutexB.release() if __name__ == '__main__': for i in range(10): t=MyThread() t.start()
携程
单线程下实现并发