一GIL(全局解释器锁)
什么是GIL锁:
GIL也称为全局解释器锁,本质上也是一把互斥锁,是为了防止多个线程在同一时间操作解释器字节码,用来锁解释器的。
仅存在cpython解释器中,这不是python这门语言的缺点。
GIL锁保护的是解释器级别的资源,比如:引用计数,垃圾分代,但对于自定义的数据并没有保护,所以还要自己加锁。
GIL的加锁和解锁时机:
加锁的时机:在调用解释器的时候立即加锁
解锁:线程运行完毕,线程工作遇到IO会自动释放,当前线程执行时间超过限定值时也会释放,默认100纳秒
GIL的优点:
保证了cpython解释器中的内存管理线程是安全的
缺点:
互斥锁的特性使得多线程无法使用并行
GIL锁的缺点是所有解释型语言的通病
递归锁(RLock,可重入锁)
Rlock可以在同一个线程内多次执行acquire,释放锁时,有多少acquire就得有多个release,
但是本质上同一个线程多次执行acquire是没有什么意义的,其他线程必须等全部release后才能够共享资源。
所以rlock仅仅是解决了代码逻辑上的错误导致的死锁,并不能解决多个锁造成的死锁。
# 同一把RLock 多次acquire #l1 = RLock() #l2 = l1 # 不同的RLock 依然会锁死 #l1 = RLock() #l2 = RLock() def task(): l1.acquire() print(threading.current_thread().name,"拿到了筷子") time.sleep(0.1) l2.acquire() print(threading.current_thread().name, "拿到了盘子") print("吃饭") l1.release() l2.release() def task2(): l2.acquire() print(threading.current_thread().name, "拿到了盘子") l1.acquire() print(threading.current_thread().name,"拿到了筷子") print("吃饭") l2.release() l1.release() t1 = Thread(target=task) t1.start() t2 = Thread(target=task2) t2.start()
死锁
比如共有2把锁,但是一人拿到了一把并且也不释放,相互等待,这就造成了死锁
发生死锁的2中情况:
1.有不止一把锁,不同线程拿到不同锁不放
2.对同一把锁执行了多次acquire
其中第二种情况我们可以通过递归锁解决
# 同一把RLock 多次acquire #l1 = RLock() #l2 = l1 # 不同的RLock 依然会锁死 #l1 = RLock() #l2 = RLock() def task(): l1.acquire() print(threading.current_thread().name,"拿到了筷子") time.sleep(0.1) l2.acquire() print(threading.current_thread().name, "拿到了盘子") print("吃饭") l1.release() l2.release() def task2(): l2.acquire() print(threading.current_thread().name, "拿到了盘子") l1.acquire() print(threading.current_thread().name,"拿到了筷子") print("吃饭") l2.release() l1.release() t1 = Thread(target=task) t1.start() t2 = Thread(target=task2) t2.start()
信号量(Semaphore)
信号量也是一把锁,它可以规定一把锁可以同时由多少个线程拿到其执行权限,并控制最大的并发访问线程数量
把Lock比作家用厕所,那么信号量就是公共厕所
from threading import Thread,Semaphore,current_thread import time s = Semaphore(3) def task(): s.acquire() print("%s running........" % current_thread()) time.sleep(1) s.release() for i in range(20): Thread(target=task).start()
EVENT事件
指的就是一个线程需要另一个线程的运行状态来决定下一步的执行操作的时候,就要用到event事件,当event事件对象为假的时候线程就会进入阻塞,当事件对象为真的时候,则会唤醒所有线程,执行其代码,如果为真则忽略继续执行
event.wait() 等待事件对象的状态
event.set()设置为真
event,clear()重置为假
event.isset()返回状态
from threading import Event,Thread import time # 先生成一个event对象 e = 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
为什么线程的资源是共享的,还要用队列,因为队列是管道加锁,解决了自己手动加锁的问题。
因为锁的不好容易产生死锁
queue.Queue:
q = queue.Queue() q.put('hahha') print(q.get())
queue.LifoQueue()堆栈先进后出
q = queue.LifoQueue() q.put(1) q.put(2) q.put(3) print(q.get())
queue.PriorityQueue()优先级锁,数据越小优先级越高
# q = queue.PriorityQueue() 数字越小 优先级越高 q.put((10,'haha')) q.put((100,'hehehe')) q.put((0,'xxxx')) q.put((-10,'yyyy')) print(q.get())