Python GIL锁 死锁 递归锁 event事件 信号量
(全局解释器锁)
1.什么是GIL:指的是全局解释器锁,本质也是一把互斥锁。主要是保证同一进程下的多个线程将不可能在同一时间使用解释器,从而保证了解释器的数据安全(同一个进程内多个线程无法实现并行但是可以实现并发)。
2.注意:
1):GIL仅存在cpython解释器中,其他解释器不存在,并不是python语言的缺点。
2):GIL保护的是解释器级别数据的安全(比如对象的引用计数,垃圾分代数据等等),对于程序中自定义的数据没有任何保护效果所以自定义共享数据要自己加锁。
3.GIL加锁与解锁的时机
加锁:在调用解释器时立即加锁
解锁时机:当线程遇到IO操作和超过设定的时间值时解锁。
1.单核下无论是IO密集还是计算密集GIL都不会产生任何影响
2.多核下对于IO密集任务,GIL会有细微的影响,基本可以忽略
3.Cpython中IO密集任务应该采用多线程,计算密集型应该采用多进程
GIL的优点:
-
保证了CPython中的内存管理是线程安全的
GIL的缺点:
-
互斥锁的特性使得多线程无法并行
mutexA = Lock() mutexB = Lock() class MyThread(Thread): def run(self): # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发 self.func1() self.func2() def func1(self): mutexA.acquire() print('%s抢到了A锁'%self.name) # self.name等价于current_thread().name mutexB.acquire() print('%s抢到了B锁'%self.name) mutexB.release() print('%s释放了B锁'%self.name) mutexA.release() print('%s释放了A锁'%self.name) def func2(self): mutexB.acquire() print('%s抢到了B锁'%self.name) time.sleep(1) mutexA.acquire() 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()
但是本质上同一个线程多次执行acquire时没有任何意义的,其他线程必须等到RLock全部release之后才能访问共享资源。
所以Rlock仅仅是帮你解决了代码逻辑上的错误导致的死锁,并不能解决多个锁造成的死锁问题
mutexA = mutexB = RLock() # A B现在是同一把锁 class MyThread(Thread): def run(self): # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发 self.func1() self.func2() def func1(self): mutexA.acquire() print('%s抢到了A锁'%self.name) # self.name等价于current_thread().name mutexB.acquire() print('%s抢到了B锁'%self.name) mutexB.release() print('%s释放了B锁'%self.name) mutexA.release() print('%s释放了A锁'%self.name) def func2(self): mutexB.acquire() print('%s抢到了B锁'%self.name) time.sleep(1) mutexA.acquire() 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()
如果把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()
事件,表示发生了某件事情,我们可以去关注某个事件然后采取一些行动,本质上事件是用来线程间通讯的 ,用于状态同步。(多用于一个线程用来等待另一个线程)
2.语法:
from threading import Event,Thread boot_event = Event() # boot_event.clear() 回复事件的状态为False # boot_event.is_set() 返回事件的状态 # boot_event.wait()等待事件发生 ,就是等待事件被设置为True,就是一个阻塞如果有参数,表示阻塞等待几秒 # boot_event.set() 设置事件为True
3.案例:
有两条线程,一个用于启动服务器 一个用于客户端链接到服务器
条件是 服务器启动成功客户端才能链接成功!
from threading import Event,Thread import time def boot_server(): print("正在启动服务器......") time.sleep(3) print("服务器启动成功!") boot_event.set() # 标记事件已经发生了 def connect_server(): boot_event.wait() # 等待事件发生 print("链接服务器成功!") t1 = Thread(target=boot_server) t1.start() t2 = Thread(target=connect_server) t2.start()
1.Queue 先进先出队列 与多进程中的Queue使用方式完全相同,区别仅仅是不能被多进程共享。 q = Queue(3) q.put(1) q.put(2) q.put(3) print(q.get(timeout=1)) print(q.get(timeout=1)) print(q.get(timeout=1)) 2.LifoQueue 后进先出队列 该队列可以模拟堆栈,实现先进后出,后进先出 lq = LifoQueue() lq.put(1) lq.put(2) lq.put(3) print(lq.get()) print(lq.get()) print(lq.get()) 3.PriorityQueue 优先级队列 该队列可以为每个元素指定一个优先级,这个优先级可以是数字,字符串或其他类型,但是必须是可以比较大小的类型,取出数据时会按照从小到大的顺序取出 pq = PriorityQueue() # 数字优先级 pq.put((10,"a")) pq.put((11,"a")) pq.put((-11111,"a")) print(pq.get()) print(pq.get()) print(pq.get()) # 字符串优先级 pq.put(("b","a")) pq.put(("c","a")) pq.put(("a","a")) print(pq.get()) print(pq.get()) print(pq.get())
import queue """ 同一个进程下的多个线程本来就是数据共享 为什么还要用队列 因为队列是管道+锁 使用队列你就不需要自己手动操作锁的问题 因为锁操作的不好极容易产生死锁现象 """ q = queue.Queue() # 先进先出 q.put('hahha') q.put('lala') print(q.get()) # hahha print(q.get()) # ala q = queue.LifoQueue() # 后进先出 q.put(1) q.put(2) q.put(3) print(q.get()) # 3 q = queue.PriorityQueue() # 数字越小 优先级越高 q.put((10,'haha')) q.put((100,'hehehe')) q.put((0,'xxxx')) q.put((-10,'yyyy')) print(q.get()) # (-10, 'yyyy')