多线程
一、GIL全局解释器锁
1.什么是GIL
官方给出的解释是:在CPython中,这个全局解释器锁,也成为GIL,是一个互斥锁,防止多个线程在同一时间执行python字节码,这个锁是非常重要的,因为CPython的内存管理非线程安全的,很多其他的特性依赖于GIL,所以即使它影响了程序效率也无法将其直接去除
★★★总结:在CPython中,GIL会把线程的并行变成串行,导致效率降低。
需要知道的是,解释器并不只有CPython,还有PyPy,JPython等等。GIL也仅仅存在于CPython中,这并不是Python这门语言的问题,而是CPython解释器的问题!
2.GIL的加锁与解锁时机
加锁的时机:在调用解释器时立即加锁
解锁时机:
♦ 当前线程遇到了IO时释放
♦ 当前线程执行时间超过设定值的释放
3.GIL性能讨论
GIL的优点:
保证了CPython中的内存管理是线程安全的
GIL的缺点:
互斥锁的特性使得多线程无法并行
直接上代码测试:
1.计算密集型
1 from multiprocessing import Process 2 from threading import Thread 3 import os,time 4 def work(): 5 res=0 6 for i in range(100000000): 7 res*=i 8 9 10 if __name__ == '__main__': 11 l=[] 12 print(os.cpu_count()) # 本机为6核 13 start=time.time() 14 for i in range(6): 15 p=Process(target=work) #耗时 4.732933044433594 16 p=Thread(target=work) #耗时 22.83087730407715 17 l.append(p) 18 p.start() 19 for p in l: 20 p.join() 21 stop=time.time() 22 print('run time is %s' %(stop-start))
2.IO密集型
1 from multiprocessing import Process 2 from threading import Thread 3 import threading 4 import os,time 5 def work(): 6 time.sleep(2) 7 8 9 if __name__ == '__main__': 10 l=[] 11 print(os.cpu_count()) #本机为6核 12 start=time.time() 13 for i in range(4000): 14 p=Process(target=work) #耗时9.001083612442017s多,大部分时间耗费在创建进程上 15 # p=Thread(target=work) #耗时2.051966667175293s多 16 l.append(p) 17 p.start() 18 for p in l: 19 p.join() 20 stop=time.time() 21 print('run time is %s' %(stop-start))
★★总结:由上得到,当执行计算密集型的代码时,多进程的运行速度要比多线程快。而当处理IO密集型的代码时,多线程的运行速度要快于多进程的。
二、GIL与普通的互斥锁
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from threading import Thread,Lock 2 import time 3 4 a = 0 5 def task(): 6 global a 7 temp = a 8 time.sleep(0.01) 9 a = temp + 1 10 11 t1 = Thread(target=task) 12 t2 = Thread(target=task) 13 t1.start() 14 t2.start() 15 t1.join() 16 t2.join() 17 print(a)
过程分析:
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
之所以出现问题就是因为两个线程在并发的执行用一段代码,解决方案就是加锁。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from threading import Thread,Lock 2 import time 3 4 lock = Lock() 5 a = 0 6 def task(): 7 global a 8 lock.acquire() 9 temp = a 10 time.sleep(0.01) 11 a = temp + 1 12 lock.release() 13 14 t1 = Thread(target=task) 15 t2 = Thread(target=task) 16 t1.start() 17 t2.start() 18 t1.join() 19 t2.join() 20 print(a)
过程分析:
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并释放lock
5.线程2睡醒后获得CPU执行权,获取GIL继续执行代码,将temp的值1+1赋值给a,执行完毕释放CPU释放GIL,释放lock,此时a的值为2
三、死锁
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from threading import Thread,Lock,current_thread 2 import time 3 4 mutexA = Lock() 5 mutexB = Lock() 6 7 class MyThread(Thread): 8 def run(self): # 创建线程自动触发run方法,run方法内调用了func1 func2相当于也是自动触发 9 self.func1() 10 self.func2() 11 12 def func1(self): 13 mutexA.acquire() 14 print('%s抢到了A锁'%self.name) #self.name 等价于 current_thread 15 mutexB.acquire() 16 print('%s抢到了B锁'%self.name) 17 mutexB.release() 18 print('%s释放了B锁'%self.name) 19 mutexA.release() 20 print('%s释放了A锁'%self.name) 21 22 def func2(self): 23 mutexB.acquire() 24 print('%s抢到了B锁'%self.name) 25 time.sleep(1) 26 mutexA.acquire() 27 print('%s抢到了A锁'%self.name) 28 mutexA.release() 29 print('%s释放了A锁'%self.name) 30 mutexB.release() 31 print('%s释放了B锁'%self.name) 32 33 for i in range(10): 34 t = MyThread() 35 t.start()
死锁原因:线程1执行func1,首先抢到了A锁,随后抢到了B锁,然后释放了B锁,最后释放了A锁。接着执行线程2,抢到了B锁,这时遇到了IO操作,中途睡了1秒,在这时间内线程2开始执行func1,首先抢到了A锁,但是紧接着在抢B锁时,失败了。因为此时A锁还被线程1抢着,1秒后线程1睡醒了,要接着抢A锁了,但此时A锁被线程2抢占了,线程2只有抢到B锁,才能继续往下走,而线程1必须抢到A锁才能继续执行下一步。那么此时就陷入了死锁状态。
四、递归锁
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from threading import Thread,RLock,current_thread 2 import time 3 4 mutexA = mutexB = RLock() # A B现在是同一把锁 5 6 7 class MyThread(Thread): 8 def run(self): # 创建线程自动触发run方法,run方法内调用了func1 func2相当于也是自动触发 9 self.func1() 10 self.func2() 11 12 def func1(self): 13 mutexA.acquire() 14 print('%s抢到了A锁'%self.name) #self.name 等价于 current_thread 15 mutexB.acquire() 16 print('%s抢到了B锁'%self.name) 17 mutexB.release() 18 print('%s释放了B锁'%self.name) 19 mutexA.release() 20 print('%s释放了A锁'%self.name) 21 22 def func2(self): 23 mutexB.acquire() 24 print('%s抢到了B锁'%self.name) 25 time.sleep(1) 26 mutexA.acquire() 27 print('%s抢到了A锁'%self.name) 28 mutexA.release() 29 print('%s释放了A锁'%self.name) 30 mutexB.release() 31 print('%s释放了B锁'%self.name) 32 33 for i in range(10): 34 t = MyThread() 35 t.start()
▶ Rlock可以被第一个抢到锁的人连续的acquire和release
▶ 每acquire一次锁身上的计数加1
▶ 每release一次锁身上的计数减1
▶ 只要锁的计数不为0 其他人都不能抢
五、信号量
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from threading import Semaphore,Thread 2 import time 3 import random 4 5 sm = Semaphore(5) # 就好比制造了一个五个坑位的厕所 6 7 def task(name): 8 sm.acquire() 9 print('%s占了一个位子'%name) 10 time.sleep(1) 11 sm.release() 12 13 for i in range(40): 14 t = Thread(target=task,args=(i,)) 15 t.start()
与递归锁的不同点:递归锁只允许一个线程抢占,而信号量可以指定抢占的数量
六、event事件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 from threading import Thread,Event 2 import time 3 4 #先生成一个对象 5 e = Event() 6 n = 3 7 def light(): 8 global n 9 print('红灯正亮着') 10 for i in range(3): 11 time.sleep(1) 12 print('\r%s'%n,end='') 13 n -= 1 14 time.sleep(1) 15 e.set() #发信号 16 print('\rGo!\r') 17 18 def car(name): 19 print('%s正在烧胎!'%name) 20 e.wait() #等待信号 21 print('%s弹射起步!'%name) 22 23 t = Thread(target=light) 24 t.start() 25 26 for i in range(1,11): 27 t = Thread(target=car,args=('秋名山车神%s'%i,)) 28 t.start()
七、线程queue
首先带着一个疑问,同一个线程中多个线程本身就可以共享数据,为什么还还要用到队列?
因为队列是管道+锁,使用队列的话你就不需要自己动手操作锁的问题,因为锁操作的不好极容易产生死锁现象
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import queue 2 3 q = queue.Queue() 4 q.put('heiheihei') 5 print(q.get())
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 q = queue.LifoQueue() 2 q.put(1) 3 q.put(2) 4 q.put(3) 5 print(q.get()) #堆栈
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 q = queue.PriorityQueue() 2 #数字越小,优先级就越高 3 q.put((10,'bibibi')) 4 q.put((100,'gugugu')) 5 q.put((50,'lalala')) 6 q.put((0,'xixixi')) 7 print(q.get())