8.14并发编程(三)
一、GIL全局解释器锁
Python解释器有很多种,最常见的就是CPython解释器
GIL本质也是一把互斥锁:将并发变成串行牺牲效率保证数据的安全
用来阻止同一个进程下的多个线程的同时执行(同一个进程内多个线程无法实现并行但是可以实现并发)
1.Python的多线程没法利用多核优势,是不是就是没有用了?
GIL的存在是因为CPython解释器的内存管理不是线程安全的
比如:垃圾回收机制
研究Python的多线程是否有用需要分情况讨论:
第一种:如果有四个任务,是计算密集型的,需要10s
(1)单核情况下
开线程更省资源
(2)多核情况下
开进程,需要10s
开线程,需要40s
第二种:如果有四个任务,是IO密集型的
(1)单核情况下
开线程更节省资源
(2)多核情况下
开线程更节省资
2.计算密集型
# 计算密集型 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()) # 本机为6核 start=time.time() for i in range(6): p=Process(target=work) #耗时 4.732933044433594 # p=Thread(target=work) #耗时 22.83087730407715 l.append(p) p.start() for p in l: p.join() stop=time.time() print('run time is %s' %(stop-start))
3.IO密集型
# IO密集型 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()) #本机为6核 start=time.time() for i in range(400): 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))
Python的多线程到底有没有用,需要看情况而定,并且肯定是有用的
多进程 + 多线程配合使用
二、GIL与普通的互斥锁
from threading import Thread import time n = 100 def task(): global n tmp = n # time.sleep(1) # IO操作时会自动释放GIL锁 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.Lock
from threading import Thread,Lock import time mutexA = Lock() mutexB = Lock() class MyThread(Thread): def run(self): # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发 self.func1() self.func2() def func1(self): mutexA.acquire() # 2号抢了A锁 print('%s抢到了A锁'%self.name) # self.name等价于current_thread().name mutexB.acquire() # 2号此时抢不到B锁,被下面的1号抢了 print('%s抢到了B锁'%self.name) mutexB.release() print('%s释放了B锁'%self.name) mutexA.release() print('%s释放了A锁'%self.name) def func2(self): mutexB.acquire() # 1号抢了B锁 print('%s抢到了B锁'%self.name) time.sleep(1) mutexA.acquire() # 1号此时抢不到A锁,被上面的2号抢了 print('%s抢到了A锁' % self.name) mutexA.release() print('%s释放了A锁' % self.name) mutexB.release() print('%s释放了B锁' % self.name) for i in range(10): t = MyThread() t.start()
注意:自己千万不要轻易的处理锁的问题
2.递归锁:RLock
RLock可以被第一个抢到锁的人连续的acquire和release
每acquire一次锁身上的计数加1
每release一次锁身上的计数减1
只要锁的计数不为0,其他人都不能抢
from threading import Thread,RLock import time mutexA = mutexB = RLock() # A B现在是同一把锁 class MyThread(Thread): def run(self): # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发 self.func1() self.func2() def func1(self): mutexA.acquire() # A B为同一把锁,锁计数加1,为1 print('%s抢到了A锁'%self.name) # self.name等价于current_thread().name mutexB.acquire() # 锁计数加1,为2 print('%s抢到了B锁'%self.name) mutexB.release() # 锁计数减1,为1 print('%s释放了B锁'%self.name) mutexA.release() # 锁计数减1,为0,被释放 print('%s释放了A锁'%self.name) def func2(self): mutexB.acquire() # 锁计数加1,为1 print('%s抢到了B锁'%self.name) time.sleep(1) mutexA.acquire() # 锁计数加1,为2 print('%s抢到了A锁' % self.name) mutexA.release() # 锁计数减1,为1 print('%s释放了A锁' % self.name) mutexB.release() # 锁计数减1,为0,被释放 print('%s释放了B锁' % self.name) for i in range(10): t = MyThread() t.start()
3.补充:
只要类加括号实例化对象
无论传入的参数是否一样生成的对象肯定不一样
单例模式除外
class Demo(object): pass obj1 = Demo() obj2 = Demo() print(id(obj1),id(obj2)) # 内存地址不一样
四、信号量
信号量可能在不同的领域中,对应不同的知识点
互斥锁:可看为一个厕所(一个坑位)
信号量:可看为公共厕所(多个坑位)
1.怎么用:Semaphore
from threading import Semaphore,Thread import time import random sm = Semaphore(5) # 造了一个含有五个的坑位的公共厕所 def task(name): sm.acquire() print('%s占了一个坑位'%name) time.sleep(random.randint(1,3)) sm.release() for i in range(40): t = Thread(target=task,args=(i,)) t.start()
五、Event事件
导入Event,set():发信号,wait():等待信号
一个子进程或子线程给另一个发信号,另一个接收到信号就可以运行了
from threading import Event,Thread import time e = Event() # 先生成一个event对象 def light(): print('红灯正亮着') time.sleep(3) e.set() # 发信号 print('绿灯亮了') def car(name): print('%s正在等红灯'%name) e.wait() # 等待信号 print('%s加油门飙车了'%name) # 接收到信号走下面代码 t = Thread(target=light) t.start() for i in range(10): t = Thread(target=car,args=('老司机%s'%i,)) t.start()
六、线程queue
同一个进程下的多个线程本来就是数据共享,为什么还要用队列?
因为队列是管道 + 锁,使用队列你就不需要自己手动操作锁的问题
因为锁操作的不好极容易产生死锁现象
1.Queue:先进先出
q = queue.Queue() q.put('hahaha') q.put('hehehe') print(q.get()) # hahaha
2.LifoQueue:后进先出
q = queue.LifoQueue() q.put(1) q.put(2) q.put(3) print(q.get()) # 3
3.PriorityQueue:优先级队列
支持你给放入队列中的数据设置优先级
数字越小,取出时优先级越高
q = queue.PriorityQueue() q.put((10,'hahaha')) q.put((100,'hehehe')) q.put((-10,'xxxx')) # 数字越小,优先级越高 q.put((0,'yyyy')) print(q.get()) # (-10, 'xxxx')