1、GIL全局解释器锁python
一、什么是全局解释器锁web
GIL本质就是一把互斥锁,至关于执行权限,每一个进程内都会存在一把GIL,同一进程内的多个线程,必须抢到GIL以后才能使用Cpython解释器来执行本身的代码,即同一进程下的多个线程没法实现并行,可是能够实现并发。安全
#1 全部数据都是共享的,这其中,代码做为一种数据也是被全部线程共享的(test.py的全部代码以及Cpython解释器的全部代码)
#2 全部线程的任务,都须要将任务的代码当作参数传给解释器的代码去执行,即全部的线程要想运行本身的任务,首先须要解决的是可以访问到解释器的代码。
例以下面多个线程的执行过程:多线程
多个线程先访问到解释器的代码,即拿到执行权限,而后将target的代码交给解释器的代码去执行并发
解释器的代码是全部线程共享的,因此垃圾回收线程也可能访问到解释器的代码而去执行,这就致使了一个问题:对于同一个数据100,可能线程1执行x=100的同时,而垃圾回收执行的是回收100的操做,解决这种问题没有什么高明的方法,就是加锁处理,以下图的GIL,保证python解释器同一时间只能执行一个任务的代码app
二、为何要用GILdom
由于CPython解释器的垃圾回收机制不是线程安全的socket
2、GIL与LOCKide
锁的目的 :锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据性能
(1)GIL是保护解释器级别的锁,使利用CPython解释器时并发使用
(2)LOCK是自定义的锁,用来保证多线程/进程对同一个数据进行修改时的数据安全,使修改数据时串行
全部线程抢的是GIL锁,或者说全部线程抢的是执行权限
线程1抢到GIL锁,拿到执行权限,开始执行,而后加了一把Lock,尚未执行完毕,即线程1还未释放Lock,有可能线程2抢到GIL锁,开始执行,执行过程当中发现Lock尚未被线程1释放,因而线程2进入阻塞,被夺走执行权限,有可能线程1拿到GIL,而后正常执行到释放Lock。。。这就致使了串行运行的效果
既然是串行,那咱们执行
t1.start()
t1.join
t2.start()
t2.join()
这也是串行执行啊,为什么还要加Lock呢,需知join是等待t1全部的代码执行完,至关于锁住了t1的全部代码,而Lock只是锁住一部分操做共享数据的代码
注意点 :
#1.线程抢的是GIL锁,GIL锁至关于执行权限,拿到执行权限后才能拿到互斥锁Lock,其余线程也能够抢到GIL,但若是发现Lock仍然没有被释放则阻塞,即使是拿到执行权限GIL也要马上交出来 #2.join是等待全部,即总体串行,而锁只是锁住修改共享数据的部分,即部分串行,要想保证数据安全的根本原理在于让并发变成串行,join与互斥锁均可以实现,毫无疑问,互斥锁的部分串行效率要更高
3、GIL与多线程
一、进程能够利用多核,可是开销大,而python的多线程开销小,但却没法利用多核优点
===>
#1. cpu究竟是用来作计算的 #2. 多cpu,意味着能够有多个核并行完成计算,因此多核提高的是计算性能 #3. 每一个cpu一旦遇到I/O阻塞,仍然须要等待,因此多核对I/O操做没什么用处
所以,对计算来讲,cpu越多越好,可是对于I/O来讲,再多的cpu也没用。固然对运行一个程序来讲,随着cpu的增多执行效率确定会有所提升(无论提升幅度多大,总会有所提升),这是由于一个程序基本上不会是纯计算或者纯I/O,因此咱们只能相对的去看一个程序究竟是计算密集型仍是I/O密集型,从而进一步分析python的多线程到底有无用武之地
#分析: 咱们有四个任务须要处理,处理方式确定是要玩出并发的效果,解决方案能够是: 方案一:开启四个进程 方案二:一个进程下,开启四个线程 #单核状况下,分析结果: 若是四个任务是计算密集型,没有多核来并行计算,方案一徒增了建立进程的开销,方案二胜 若是四个任务是I/O密集型,方案一建立进程的开销大,且进程的切换速度远不如线程,方案二胜 #多核状况下,分析结果: 若是四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜 若是四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜 #结论:
如今的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提高,甚至不如串行(没有大量切换),可是,对于IO密集型的任务效率仍是有显著提高的。
===>多核状况下,Python对于计算密集型,使用多进程的效率高
而对于I/O密集型,Python使用多线程的效率高一点
二、测试
from multiprocessing import Process from threading import Thread import os,time def work(): res=0 for i in range(10000): res*=i if __name__ == '__main__': l=[] print(os.cpu_count()) start=time.time() for i in range(4): p=Process(target=work) #p=Thread(target=work) 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()) start=time.time() for i in range(400): # p=Process(target=work) p=Thread(target=work) l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
应用场景:
多线程用于IO密集型,如socket,爬虫,web
多进程用于计算密集型,如金融分析
4、死锁现象与递归锁
一、死锁现象
两个或两个以上的进程或线程在执行过程当中,因争夺资源而形成的一种互相等待的现象,若无外力做用,它们都将没法推动下去。此时称系统处于死锁状态或系统产生了死锁(例如a持有B房间的钥匙,却被所在A房间,而b持有A房间的钥匙,却被锁在B房间,两人都被锁了),这些永远在互相等待的进程称为死锁进程
以下就是死锁
from threading import Thread,Lock,RLock import time mutexA=Lock() mutexB=Lock() class Mythead(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(2) mutexA.acquire() print('%s 抢到了A锁' %self.name) mutexA.release() mutexB.release() if __name__ == '__main__': for i in range(100): t=Mythead() t.start()
二、死锁的解决方法——递归锁
在Python中为了支持在同一线程中屡次请求同一资源,python提供了可重入锁RLock
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源能够被屡次require。直到一个线程全部的acquire都被release,其余的线程才能得到资源。上面的例子若是使用RLock代替Lock,则不会发生死锁:
mutexA=mutexB=threading.RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的状况,则counter继续加1,这期间全部其余线程都只能等待,等待该线程释放全部锁,即counter递减到0为止
from threading import Thread,Lock,RLock import time # mutexA=Lock() # mutexB=Lock() mutexB=mutexA=RLock() class Mythead(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(2) mutexA.acquire() print('%s 抢到了A锁' %self.name) mutexA.release() mutexB.release() if __name__ == '__main__': for i in range(100): t=Mythead() t.start()
5、信号量
Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其余线程调用release()。
===>一直有n个线程并发运行,可是这n个线程是变化的,不是最初的n个线程在运行
from threading import Thread,Semaphore import time,random sm=Semaphore(5) #容许5个线程并发运行,可是5个线程能够不是原来那5个 def task(name): sm.acquire() print('%s 正在上厕所' %name) time.sleep(random.randint(1,3)) sm.release() if __name__ == '__main__': for i in range(20): t=Thread(target=task,args=('路人%s' %i,)) t.start()
6、Event
当一个线程须要经过判断其余线程的状态来决定下一步的操做,能够利用Event来实现
event.isSet():返回event的状态值; event.wait():若是 event.isSet()==False将阻塞线程; event.set(): 设置event的状态值为True,全部阻塞池的线程激活进入就绪状态, 等待操做系统调度; event.clear():恢复event的状态值为False。
from threading import Thread,Event import time event=Event() def light(): print('红灯正亮着') time.sleep(3) event.set() #绿灯亮 def car(name): print('车%s正在等绿灯' %name) event.wait() #等灯绿 print('车%s通行' %name) if __name__ == '__main__': # 红绿灯 t1=Thread(target=light) t1.start() # 车 for i in range(10): t=Thread(target=car,args=(i,)) t.start()
7、进程队列(比较互斥锁,推荐使用队列)
一、queue队列 :使用import queue,用法与进程Queue同样
二、三种队列
(1). Queue:先进先出队列
import queue q=queue.Queue() q.put('first') q.put('second') q.put('third') print(q.get()) print(q.get()) print(q.get()) ''' 结果(先进先出): first second third '''
(2). LifoQueue:先进后出队列(至关于堆栈)
import queue q=queue.Queue() q.put('first') q.put('second') q.put('third') print(q.get()) print(q.get()) print(q.get()) ''' 结果(先进先出): third second first '''
(3). ProirityQueue:优先级队列
put进入一个元组,元组的第一个元素是优先级(一般是数字,也能够是非数字之间的比较),数字越小优先级越高
import queue q=queue.PriorityQueue() #put进入一个元组,元组的第一个元素是优先级(一般是数字,也能够是非数字之间的比较),数字越小优先级越高 q.put((20,'a')) q.put((10,'b')) q.put((30,'c')) print(q.get()) print(q.get()) print(q.get()) ''' 结果(数字越小优先级越高,优先级高的优先出队): (10, 'b') (20, 'a') (30, 'c') '''