1 Lock/RLock 普通锁(互斥锁)
线程在同一个进程内是共享资源的,很容易发生资源的争抢产生脏数据。互斥锁的作用是解决资源争抢的问题,允许某一部分资源,同时只有一个线程访问。注意:尽量在访问公共资源的时候再使用互斥锁。
1.1Lock原始锁
Lock 称为原始锁,获取锁之前不做判断,直到获取锁为止
1.1.1Lock()实例方法
1 acquire():尝试获得锁定。使得线程进入同步阻塞状态。
2 release():释放锁,使用前线程必须已经获得锁定,否则将抛出异常。
1.1.2小案例
import threading
import time
num=0
# def add_num(i):
# lock.acquire()
# global num
# time.sleep(1)
# num += i
# print(f"num is {num}")
# lock.release()
def add_num(i):
# 使用with语句管理对象 -- 上下文管理器对象
# 上下文管理器对象 __enter__, __exit__
# __enter__方法做一些初始化工作
# __exit__方法做一些结束收尾工作 释放资源
with lock:
global num
time.sleep(1)
num += i
print(f"num is {num}")
t_list = []
# 生成一个锁实例
lock = threading.Lock()
for i in range(10):
t = threading.Thread(target=add_num, args=(i,))
t.start()
[t.join() for t in t_list]
print("ending......")
1.1.3死锁现象
如果使用的是Lock(原始锁),在同一个进程,获取原始锁之后,这个锁还没有被释放掉又去尝试获取同一个原始锁,就会产生死锁现象。而使用的是RLock(重入锁),由于它会判断自己是否已经获得了锁,所以它一般不会发生死锁现象。
1.2RLock重入锁
RLock 称为重入锁,获取锁之前先判断,如果自己有了锁,就立即返回
1.2.1 小案例
import threading
lock1 = threading.Lock()
lock2 = threading.RLock()
# 这种情况会产生死锁
# lock1.acquire()
# print("lock1 acqurie 1")
# lock1.acquire() # 同一个进程,获取原始锁之后,没有释放又去尝试获取同一个 原始锁,就会产生死锁
# print("lock1 acquire 2")
# lock1.release()
# print("lock1 release 1")
# lock1.release()
# print("lock1 release 2")
lock2.acquire()
print("lock2 acqurie 1")
lock2.acquire()
print("lock2 acquire 2")
lock2.release()
print("lock2 release 1")
lock2.release()
print("lock2 release 2")
这个代码很好地体现出了,Lock与RLock两者之间的异同。
2 信号量(Semaphore)
信号量锁最多允许同时N个线程执行内容。
2.1 构造方法
Semaphore(N): s = threading.Semaphore(N) 创建一个信号量锁对象。N是一个整型参数,表示设置同时允许执行的线程上限。
2.2 实例方法
acquire(): 尝试获得锁定,使得线程进入同步阻塞状态。
release(): 释放锁。使用前线程必须已获得锁定,否则将抛出异常。
3 事件锁
事件机制:全局定义了一个"Flag",称为标志位。如果标志位的值为False,那么当程序执行wait()就会出现阻塞;如果标志位的值为True,那么wait()方法时便不再阻塞。注意:事件锁不能利用with语句进行使用,只能按照常规方式使用。
这种锁类似于交通红绿灯(默认是红灯),它属于在红灯的时候一次性阻挡所有线程,在绿灯的时候,一次性放行所有的排队中的线程。
Event是线程间通信的机制之一:一个线程发送一个event信号,其他线程则等待这个信号。即用主线程控制其他线程的执行。
3.1 构造方法
Event(): e = threading.Event() 生成一个事件锁对象
3.2 实例方法
e.wait([timeout]): 阻塞线程,直到Event对象内部标识位被设置为True或超时,这里可以选择是否设置超时时间的参数。
set(): 将标志位设置为True。
clear(): 将标志位设置为False。
isSet(): 判断标识位是否为True。
4 条件锁
该机制会使得线程等待,只有满足某条件时,才会释放n个线程。
4.1 构造方法
Condition(): c = threading.condition()
4.2 实例方法
wait_for(func): 等待函数返回结果,如果结果为True—放行一个线程
wait()、c.notify(N): 一次性放行N个wait
acquire(): 上锁 release(): 解锁
上面的wait和wait_for()需要在上锁和解锁的中间使用
5 以上几种锁的总结
1 事件锁是基于条件锁来做的,它和条件锁的区别在于一次只能放行全部,不能放行任意个数量的子线程继续运行。
2 信号量锁也是根据条件锁来做的,它和条件锁和事件锁的区别如下:
2.1 条件锁一次可以放行任意个处于"等待"状态的线程。
2.2 事件锁一次可以放行全部的处于"等待"状态的线程。
2.3 信号量锁,通过规定,成批的放行特定个处于"上锁"状态的线程。
3 事件锁内部是基于条件锁来做的
4 信号量锁内部也是基于条件锁来做的
6 GIL全局解释器锁
全局解释器锁的英文简称是GIL(Global Interpreter Lock), GIL和Python语言没有任何关系,它只是因为历史原因导致在官方推荐的解释器CPython中遗留的问题,那CPython为什么还被官方推荐呢,是因为CPython的社区十分活跃,而且它拥有的库也十分的丰富。每个线程在执行过程中都需要先获取GIL,保证同一时刻只有一个线程可以执行代码。
GIL最基本的行为只有两个:1.当前执行的线程持有GIL 2.当线程遇到IO阻塞时,会释放GIL。
由于CIL锁的限制,所以多线程不适合计算型任务,反而更适合IO型任务
计算密集型任务就是用CPU频繁计算; IO密集型任务就是网络IO(抓取网络数据)、磁盘操作(读写文件)、键盘输入等。
计算密集型任务——>使用多进程
IO密集型任务——>使用多线程