python threading 中的 Lock, RLock, Condition, Semaphore, Event
这些内容主要是用来控制在并发过程中,线程的同步行为的。
Lock
lock
是在控制同步中 常见且基础的对象。通过这个对象可以控制共享资源的串行访问。
需要注意: 锁的获取、释放, 防止死锁!
from threading import Lock
lock = Lock()
with lock:
pass
当然可以添加两个参数, blocking
, timeout
用来控制怎么获取锁。
可以阻塞, 可以超时,默认是阻塞无超时,就是一直等待下去。
默认参数: timeout=-1, blocking=True
RLock
重入锁。
为什么会出现:当时用普通的锁的时候,如果已经获取了锁,再次获取的时候,就会永久阻塞。也就是锁不会被释放
也不能再次被获取。这就造成了死锁。
解决方式:就是提供了一个类型的锁,重入锁, RLock
特点:就是已经获取到锁后当前线程可以再次获取。
内部实现: 基本锁是Lock
, 内部是通过获取当前锁的持有人的ID
, 每次获取的时候进行判断,是同一个就返回并给计数加1。
释放则不会直接把锁释放掉,而是计数减一,直到计数为0,才真正的释放屌内部锁
from threading import RLock
r_lock = RLock()
with r_lock:
print(r_lock)
with r_lock:
print(r_lock)
输出:
<locked _thread.RLock object owner=5832 count=1 at 0x0000000002158B40>
<locked _thread.RLock object owner=5832 count=2 at 0x0000000002158B40>
可看到 count
变为了2, owner
表示持有人为5832
Condition
条件,条件变量, 状态
为什么会出现: 假如有一个需求 是在某些顺序代码中 要不止一次
的控制暂停等待
或者唤醒
。
如果单单的只是用一把普通的锁,多个任务(并发的同样的任务或是同时执行的不同的任务)也就只能停止等待一次,获取了锁,执行完就释放了。
那么如果要多次等待几次,那就同时多整几把锁,放到队列里面, 并且不同的并发的线程中的等待的锁不能一样。但是可以通过接口释放这些锁,达到唤醒目的。
同时,因为处于并发环境下,协调这些锁的时候为了避免锁队列内容不同步,需要有一把串行锁,保证协调的时候串行执行。同时也是防止等待超时之后直接顺序执行代码
怎么实现呢:
材料: 同步锁,锁队列
实现方式: 在需要等待的时候,先保证持有同步锁,生成一把普通的锁,加进锁队列,然后进行等待,同时释放转储同步锁,让其他线程使用。
同时要注意锁队列的清除,把超时的锁清除,和被唤醒的锁清除。清除完毕之后要重新获取同步锁,防止因阻塞锁超时释放而自动执行代码
通知接口: 通知接口会将所有的阻塞锁进行释放,让等待这些锁的断点进行执行。
需要注意的是:
在使用wait
,notify
的时候要先获取同步锁。
不关键的关键内容: wait
里面是怎么通过一把锁使得当前线程暂停的。
基本方式如下:
is_block = True
timeout = 3
from threading import Lock
lock_queue = []
simple_lock = Lock()
lock_queue.append(simple_lock)
simple_lock.acquire()
# 释放同步锁,让其他线程使用
simple_lock.acquire(block=is_block, timeout=timeout)
# 这样 这个顺序代码就被阻塞到这个地方了
其中这个simple_lock 就是用来阻断代码执行,只有等到 simple_lock 被其他线程释放,或是超时才能继续往下执行代码。
另外,超时之后,还要争用同步锁。
samephore
信号量
相对于普通的锁,增加了并发控制,可以认为的控制同时执行的线程个数
内部实现基于condition
event
事件
里面增加了标记,被称为事件信号,是condition的又一包装。 当标记被设置之后,唤醒所有等待事件信号的线程。
操作相对condition更加简单
内部实现:condition