Python中的锁
一、全局解释器锁(GIL)
1、什么是全局解释器锁
在同一个进程中只要有一个线程获取了全局解释器(cpu)的使用权限,那么其他的线程就必须等待该线程的全局解释器(cpu)使用权消失后才能使用全局解释器(cpu),即时多个线程直接不会相互影响在同一个进程下也只有一个线程使用cpu,这样的机制称为全局解释器锁(GIL)。
2、全局解释器锁的好处
1、避免了大量的加锁解锁的好处
2、使数据更加安全,解决多线程间的数据完整性和状态同步
3、全局解释器的缺点
多核处理器退化成单核处理器,只能并发不能并行。
同一时刻的某个进程下的某个线程只能被一个cpu所处理,所以在GIL锁下的线程只能被并发,不能被并行。
实例:
import time import threading def sub(): global num num -= 1 time.sleep(1) num = 100 # 定义一个全局变量 l = [] # 定义一个空列表,用来存放所有的列表 for i in range(100): # for循环100次 t = threading.Thread(target=sub) #每次循环开启一个线程 t.start() # 开启线程 l.append(t) # 将线程加入列表l for i in l: i.join() # 这里加上join保证所有的线程结束后才运行下面的代码 print(num) # 输出结果为0
二、同步锁
1、什么是同步锁?
同一时刻的一个进程下的一个线程只能使用一个cpu,要确保这个线程下的程序在一段时间内被cpu执,那么就要用到同步锁。
2、为什么用同步锁?
因为有可能当一个线程在使用cpu时,该线程下的程序可能会遇到io操作,那么cpu就会切到别的线程上去,这样就有可能会影响到该程 序结果的完整性。
3、怎么使用同步锁?
只需要在对公共数据的操作前后加上上锁和释放锁的操作即可。
4、实例:
import time import threading R = threading.Lock() def sub(): global num R.acquire() # 加锁,保证同一时刻只有一个线程可以修改数据 num -= 1 R.release() # 修改完成就可以解锁 time.sleep(1) num = 100 # 定义一个全局变量 l = [] # 定义一个空列表,用来存放所有的列表 for i in range(100): # for循环100次 t = threading.Thread(target=sub) # 每次循环开启一个线程 t.start() # 开启线程 l.append(t) # 将线程加入列表l for i in l: i.join() # 这里加上join保证所有的线程结束后才运行下面的代码 print(num) # 输出结果为0
5、扩展知识
1、GIL的作用:多线程情况下必须存在资源的竞争,GIL是为了保证在解释器级别的线程唯一使用共享资源(cpu)。
2、同步锁的作用:为了保证解释器级别下的自己编写的程序唯一使用共享资源产生了同步锁。
三、递归锁和死锁
1、什么是死锁?
指两个或两个以上的线程或进程在执行程序的过程中,因争夺资源而相互等待的一个现象
import time import threading A = threading.Lock() B = threading.Lock() import threading class obj(threading.Thread): def __init__(self): super().__init__() def run(self): self.a() # 如果两个锁同时被多个线程运行,就会出现死锁现象 self.b() def a(self): A.acquire() print('123') B.acquire() print(456) time.sleep(1) B.release() print('qweqwe') A.release() def b(self): B.acquire() print('asdfaaa') A.acquire() print('(⊙o⊙)哦(⊙v⊙)嗯') A.release() B.release() for i in range(2): # 循环两次,运行四个线程,第一个线程成功处理完数据,第二个和第三个就会出现死锁 t = obj() t.start()
程序会出现阻塞现象
2、什么是递归锁?
在Python中为了支持同一个线程中多次请求同一资源,Python提供了可重入锁。这个RLock内部维护着一个Lock和一个counter
变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获
得资源。
import time import threading A = threading.RLock() # 这里设置锁为递归锁 import threading class obj(threading.Thread): def __init__(self): super().__init__() def run(self): self.a() self.b() def a(self): # 递归锁,就是将多个锁的钥匙放到一起,要拿就全拿,要么一个都拿不到 # 以实现锁 A.acquire() print('123') print(456) time.sleep(1) print('qweqwe') A.release() def b(self): A.acquire() print('asdfaaa') print('(⊙o⊙)哦(⊙v⊙)嗯') A.release() for i in range(2): t = obj() t.start()
四、信号量(semaphore)
1、什么是信号量?
同进程的一样,semaphore管理一个内置的计数器,每当调用acquire()时内置函数-1,每当调用release()时内置函数+1。
计数器不能为0,当计数器为0时acquire()将阻塞线程,直到其他线程调用release()。
import threading import time mysf = threading.Semaphore(5) # 创建信号量对象,(5表示这个锁同时支持的个数) def func(): if mysf.acquire(): # 因为使用了信号量,下面的输出就会5个5个的同时输出 print(threading.currentThread().getName() + 'get semaphore') time.sleep(1) mysf.release() for i in range(20): t = threading.Thread(target=func) t.start()