一 GIL (全局解释器锁)
1.什么是GIL:指的是全局解释器锁,本质也是一把互斥锁。主要是保证同一进程下的多个线程将不可能在同一时间使用解释器,从而保证了解释器的数据安全(同一个进程内多个线程无法实现并行但是可以实现并发)。
2.注意:
1):GIL仅存在cpython解释器中,其他解释器不存在,并不是python语言的缺点。
2):GIL保护的是解释器级别数据的安全(比如对象的引用计数,垃圾分代数据等等),对于程序中自定义的数据没有任何保护效果所以自定义共享数据要自己加锁。
3.GIL加锁与解锁的时机
加锁:在调用解释器时立即加锁
解锁时机:当线程遇到IO操作和超过设定的时间值时解锁。
总结:
1.单核下无论是IO密集还是计算密集GIL都不会产生任何影响
2.多核下对于IO密集任务,GIL会有细微的影响,基本可以忽略
3.Cpython中IO密集任务应该采用多线程,计算密集型应该采用多进程
GIL的优点:
-
保证了CPython中的内存管理是线程安全的
GIL的缺点:
-
互斥锁的特性使得多线程无法并行
二 死锁(相互等待,互不释放)
1.什么是死锁: 当程序出现了不止一把锁,分别被不同的线程持有, 有一个资源 要想使用必须同时具备两把锁
这时候程序就会进程无限卡死状态 ,这就称之为死锁
2.
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()View Code
三 Rlock(递归锁也称可重入锁)
1.什么是递归锁:Rlock 同一个线程可以多次执行acquire,释放锁时,有几次acquire就要release几次。
2.注意:
但是本质上同一个线程多次执行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()View Code
四 信号量
1.什么是信号量:信号量也是一种锁,其特殊之处在于可以让一个资源同时被多个线程共享,并控制最大的并发访问线程数量。
如果把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()View Code
五 EVen 事件
1.什么是事件:事件,表示发生了某件事情,我们可以去关注某个事件然后采取一些行动,本质上事件是用来线程间通讯的 ,用于状态同步。(多用于一个线程用来等待另一个线程)
2.语法:
from threading import Event,Thread boot_event = Event() # boot_event.clear() 回复事件的状态为False # boot_event.is_set() 返回事件的状态 # boot_event.wait()等待事件发生 ,就是等待事件被设置为True,就是一个阻塞如果有参数,表示阻塞等待几秒 # boot_event.set() 设置事件为TrueView Code
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()View Code
六 线程间通信
1.线程间的通信:同一进程下线程间的数据本来就是共享的,但需要自己加锁处理数据安全和错乱的问题。队列=管道+锁,即可通信又解决了加锁的问题。
2.
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')View Code