Lock 对象
原语锁 (互斥锁) 是一个同步原语, 状态是 "已锁定" 或者 "未锁定" 之一. 两个方法 acquire()和 release()用于修改锁的状态. 如果状态为已锁定,
尝试获取锁将被阻塞, 直到锁被释放为止. 如果有多个线程等待获取锁, 当锁被释放时, 只有一个线程能获取它, 等待线程获得锁的顺序没有定义.
使用下面的构造函数可以创建新的 Lock 实例:
Lock()
创建新的 Lock 对象, 初始状态为未锁定
Lock 实例 lock 支持一下方法
lock.acquire([blocking])
获取锁, 如果有必要, 需要阻塞到锁释放为止. 如果提供 blocking 参数并将它设为 False, 当无法获取锁时立即返回 False,
如果成功获取锁则返回 True.
lock.release()
释放一个锁. 当锁处于未锁定状态时, 或者从与原本调用 acquire()方法的线程不同的线程调用此方法, 将出现错误
- # 未加锁, Windows 下运行不会出现混乱, 在 Linux 下运行就会发行结果出现混乱
- import threading
- import time
- num = 0
- def show():
- global num
- time.sleep(3)
- num += 1
- print(num)
- for i in range(10):
- t = threading.Thread(target=show)
- t.start()
- print('thread stop')
Linux 下的结果(多运行几遍就会发现结果混乱)
- 1
- 2
- 3
- 5
- 7
- 4
- 8
- 6
- 9
- 10
- # 加锁
- import threading
- import time
- clock = threading.Lock()
- num = 0
- def show():
- clock.acquire()
- global num
- time.sleep(3)
- num += 1
- print(num)
- clock.release()
- for i in range(10):
- t = threading.Thread(target=show)
- t.start()
- print('thread stop')
Rlock 对象
可重入锁是一个类似于 Lock 对象的同步原语, 但同一个线程可以多次获取它. 这允许拥有锁的线程执行嵌套的 acquire()和 release()操作. 在这种
情况下, 只有对外面的 release()操作才能将锁重置为未锁定状态.
使用以下的构造函数可以创建一个新的 RLock 对象:
RLock()
创建新的可重入锁对象. RLock 对象 rlock 支持一下方法.
rlock.acquire([blocking])
获取锁, 如果有必要, 需要阻塞到锁被释放为止. 如果没有线程拥有锁, 它将被锁定, 而且递归级别被置为 1. 如果此线程已经拥有锁, 锁的递归级别加 1,
而且函数立即返回.
rlock.release()
通过减少锁的递归级别来释放它. 如果在减值后递归级别为 0, 锁将被重置为未锁定状态. 否则, 锁将保持以锁定状态. 只能由目前拥有锁的线程来调用此函数.
在使用诸如 Lock,RLcok 或 Semphore 之类的锁原语时, 必须多加小心. 锁的错误管理经常导致死锁或竞争条件. 依赖的代码应该保证出现异常时正确地释放锁.
典型的代码如下:
- try:
- lock.acquire()
- # 关键部分
- statements
- ...
- finally:
- lock.release()
- # 另外, 所有种类的锁还支持上下文管理协议(写起来更简洁些)
- with lock:
- # 关键部分
- # with 语句自动获取锁, 并且在控制流离开上下文时自动释放锁
- # 此外, 编写代码时一般应该避免同时获取多个锁例如:
- with lock_A:
- # 关键部分
- statements
- ...
- with lock_B:
- # B 的关键部分
- ...
这通常很容易导致应用程序神秘死锁. 尽管有几种策略可以避免出现这种情况(如分层锁定), 但最好在编写代码时就避免这种做法
信号量
信号量是一个基于计数器的同步原语, 每一次调用 acquire()方法时此计数器减 1, 每次调用 release()方法时此计数器加 1. 如果计数器为 0,acquire()方法将会阻塞,
直到其他线程调用 release()方法为止.
Semaphore([value])
创建一个新的信号量. value 是计数器的初始值. 如果省略此参数, 计数器的值将被置为 1.
Semaphore 实例 s 支持一下方法
s.acquire([blocking])
获取信号量. 如果进入时内部计数器大于 0, 此方法将把它的值减 1, 然后立即返回. 如果它的值为 0, 此方法将阻塞, 知道另一个线程调用 release()方法为止.
blocking 参数的行为与 Lock 和 RLock 对象中描述的相同
s.release()
通过将内部计数器的值加 1 来释放一个信号量. 如果计数器为 0, 而且另一个线程正在等待, 该线程将被唤醒. 如果有多个线程正在等待, 只能从它的 acquire()
调用返回其中一个. 线程释放的顺序并不确定
BoundedSemaphore([value])
创建一个新的信号量. Value 是计数器的初始值. 如果省略此参数, 计数器的值将被置为 1.BounderSemaphore 的工作方式与 Semaphore 完全相同,
但 release()操作的次数不能超过 acquire()操作的次数
信号量与互斥锁之间的微妙差别在于: 信号量可以用于发信号. 例如, 可以从不同的线程调用 acquire()和 release()方法, 以便在生产者和消费者线程之间进行通信.
- import threading
- import time
- produced = threading.Semaphore(3)
- consumed = threading.Semaphore(2)
- def producer():
- for i in range(10):
- consumed.acquire()
- time.sleep(3)
- print('-----------------', time.ctime())
- consumed.release()
- def consumer():
- for j in range(10,20):
- produced.acquire()
- time.sleep(3)
- print('=================', time.ctime())
- consumed.release()
- for i in range(20):
- t1 = threading.Thread(target=producer)
- t2 = threading.Thread(target=consumer)
- t1.start()
- t2.start()
运行结果
- ----------------- Fri Dec 14 16:11:08 2018
- ================= Fri Dec 14 16:11:08 2018
- ----------------- Fri Dec 14 16:11:08 2018
- ================= Fri Dec 14 16:11:08 2018
- ================= Fri Dec 14 16:11:08 2018
- ----------------- Fri Dec 14 16:11:11 2018
- ----------------- Fri Dec 14 16:11:11 2018
- ----------------- Fri Dec 14 16:11:11 2018
- ----------------- Fri Dec 14 16:11:11 2018
- ----------------- Fri Dec 14 16:11:11 2018
- ----------------- Fri Dec 14 16:11:14 2018
事件
事件用于在线程之间通信. 一个线程发出 "事件" 信号, 一个或多个其他线程等待它, Event 实例管理着一个内部标志, 可以使用 set()方法将它置为 True, 或者
使用 clear()方法将它重置为 False.wait()方法将阻塞, 直到标志为 True
Event()
创建新的 Event 实例, 并将内部标志置为 False.Event 实例 e 支持一下方法
e.is_set()
只有当内部标志为 True 时才返回 True. 在老式代码中此方法叫 isSet()
e.set()
内部标志为 True. 等待它变为 True 的所有线程都将被唤醒
e.clear()
将内部标志重置为 False
e.wait([timeout])
阻塞直到内部标志为 True. 如果进入时内部标志为 True, 此方法将立即返回. 否则它将阻塞, 直到另一个线程调用 set()方法将标志置为 True, 或者直到出现可选的
超时. timeout 是一个浮点数, 用于指定以秒为单位的超时期限
- import threading
- import time
- event = threading.Event()
- def chihuoguo():
- # 等待事件, 进入等待阻塞状态
- print('%s 进入准备状态' % threading.currentThread().getName())
- time.sleep(1)
- event.wait()
- # 收到事件后进入运行状态
- print('%s 开始运行' % threading.currentThread().getName())
- # 设置线程组
- threads = []
- # 创建新线程
- thread1 = threading.Thread(target=chihuoguo, name='one')
- thread2 = threading.Thread(target=chihuoguo, name='two')
- # 添加到线程组
- threads.append(thread1)
- threads.append(thread2)
- # 开启线程
- for thread in threads:
- thread.start()
- time.sleep(0.1)
- # 发送事件通知
- print('event start')
- event.set()
使用 event.clear()
- import threading
- import time
- event = threading.Event()
- def chihuoguo():
- for i in range(1, 5):
- # 等待事件, 进入等待阻塞状态
- print('%s %s 进入准备状态' % (threading.currentThread().getName(), i))
- time.sleep(1)
- event.wait()
- if i == 3:
- event.clear()
- # 收到事件后进入运行状态
- print('%s %s 开始运行' % (threading.currentThread().getName(), i))
- # 设置线程组
- threads = []
- # 创建新线程
- thread1 = threading.Thread(target=chihuoguo, name='one')
- thread2 = threading.Thread(target=chihuoguo, name='two')
- # 添加到线程组
- threads.append(thread1)
- threads.append(thread2)
- # 开启线程
- for thread in threads:
- thread.start()
- time.sleep(0.1)
- # event.clear()
- # 发送事件通知
- print('event start')
- event.set()
尽管 Event 对象可用于给其他线程发信号, 但不应该使用它们来实现在生成者 / 消费者问题中十分典型的通知. 例如, 应该避免写出下面这样的代码
- evt = threading.Event()
- def producer():
- while True:
- # 生产者
- ...
- evt.signal()
- def consumer():
- while True:
- # 一个等待项
- evt.wait()
- # 消费项
- ...
- # 清除事件并再次等待
- evt.clear()
这段代码并不可靠, 因为在 evt.wait()和 evt.clear()操作之间, 生产者可能产生了一个新项. 但是, 通过清除事件, 在生产者创建一个新项之前, 消费者可能看不到
这个新项. 最好的情况是, 程序将经过一段很短的停滞, 对项的处理被莫名其妙推迟了. 最坏的情况是, 由于事件信号丢失, 整个程序将挂起. 要解决这类问题最好使用
条件变量
来源: https://www.cnblogs.com/niuu/p/10120341.html