这篇技术文章讨论了多线程编程中的几个重要概念。它首先介绍了等待事件的使用,并强调了避免使用“For Loop & Sleep”进行等待的重要性。接着,文档解释了竞态条件,并提供了处理共享资源的建议,即在使用共享资源时进行加锁和解锁操作,并且尽快释放锁。文档还简要提到了死锁和线程同步的问题,并通过图示说明了锁的获取和释放过程。最后,它介绍了信号量的概念,说明了它如何控制对共享资源的访问线程数量。
我们来详细了解一下 Python threading 模块中的 event.set() 方法,并举例说明其用法。
event.set() 的作用
在 Python 的多线程编程中,threading.Event 对象是一种简单而有效的线程间通信机制。它内部维护一个标志(flag),这个标志初始时为 False。
event.set() 方法的主要作用是将这个内部标志设置为 True。
与 event.set() 相关的主要方法
理解 event.set() 的作用,还需要了解 Event 对象的其他几个关键方法:
• event.wait(timeout=None):
如果内部标志为 True,则此方法立即返回 True。
如果内部标志为 False,则此方法会阻塞当前线程,直到其他线程调用 event.set() 将标志设置为 True。
如果设置了 timeout 参数(一个浮点数,表示秒数),则线程最多阻塞 timeout 秒。如果在超时前标志被设置为 True,则返回 True;如果超时后标志仍为 False,则返回 False。
• event.clear(): 将内部标志重新设置为 False。
• event.is_set(): 返回内部标志的当前状态(True 或 False),不会阻塞线程。
event.set() 的核心用途
event.set() 通常用于:
代码如下(示例):
下面通过一个例子来演示 event.set() 的使用:
import threading
import time
# 创建一个 Event 对象
event = threading.Event()
def worker_thread(name):
print(f"线程 {name}: 开始工作,等待事件...")
# 等待事件被设置 (event.set() 被调用)
event.wait()
print(f"线程 {name}: 事件已被触发,继续执行...")
time.sleep(1) # 模拟一些工作
print(f"线程 {name}: 工作完成。")
def control_thread():
print("控制线程: 启动,将在3秒后触发事件。")
time.sleep(3)
print("控制线程: 现在触发事件!")
event.set() # 设置事件标志为 True,唤醒等待的线程
if __name__ == "__main__":
# 创建并启动工作线程
thread1 = threading.Thread(target=worker_thread, args=("Worker-1",))
thread2 = threading.Thread(target=worker_thread, args=("Worker-2",))
thread1.start()
thread2.start()
# 创建并启动控制线程
controller = threading.Thread(target=control_thread)
controller.start()
# 等待所有线程完成
thread1.join()
thread2.join()
controller.join()
print("所有线程执行完毕。")
小结:
threading.Event 和其 set() 方法是 Python 多线程编程中一种非常实用的同步原语。它允许一个或多个线程等待某个特定条件的发生,而另一个线程则可以在条件满足时通过调用 set() 来通知这些等待的线程。这使得线程间的协作和流程控制更加灵活和可控。
希望这个解释和例子能帮助你理解 event.set() 的用法!
我们来讨论 Python 中的“竞争条件”(Race Condition)以及如何通过例子来理解它。
什么是竞争条件 (Race Condition)?
竞争条件发生在多个线程或进程并发地访问和修改共享数据时,最终的结果取决于这些线程或进程执行操作的相对顺序。由于操作系统调度线程/进程的方式具有不确定性,这种相对顺序往往是不可预测的,从而可能导致程序出现意料之外的、不一致的或错误的行为。
简单来说,就是当多个执行路径(线程/进程)试图同时“竞争”去操作同一个资源时,如果缺乏适当的同步机制,就会导致混乱。
竞争条件发生的关键要素:
代码如下(示例):
下面是一个经典的例子,演示了多线程在没有同步的情况下修改共享变量时如何产生竞争条件:
import threading
import time
# 共享变量
shared_counter = 0
# 迭代次数
ITERATIONS = 100000
def increment_counter():
global shared_counter
for _ in range(ITERATIONS):
# 这里是竞争条件发生的核心区域
current_value = shared_counter
# 模拟一些处理时间,增加竞争条件发生的概率
# time.sleep(0.0000001) # 即使是很短的时间也可能导致问题
shared_counter = current_value + 1
if __name__ == "__main__":
threads = []
num_threads = 5
print(f"初始计数器值: {shared_counter}")
print(f"每个线程将计数器增加 {ITERATIONS} 次")
print(f"理论上的最终计数器值应为: {num_threads * ITERATIONS}")
for i in range(num_threads):
thread = threading.Thread(target=increment_counter)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
print(f"实际的最终计数器值: {shared_counter}")
print(f"与理论值的差额: {(num_threads * ITERATIONS) - shared_counter}")
代码解释与竞争条件的发生:
为了解决这个问题,我们需要引入同步机制,确保在任何时候只有一个线程可以修改共享资源。threading.Lock 是一个常用的同步原语。
import threading
import time
# 共享变量
shared_counter = 0
ITERATIONS = 100000
# 创建一个锁对象
lock = threading.Lock()
def increment_counter_safe():
global shared_counter
for _ in range(ITERATIONS):
# 在访问共享资源前获取锁
lock.acquire()
try:
# 现在这部分代码是互斥的,只有一个线程能执行
current_value = shared_counter
shared_counter = current_value + 1
finally:
# 确保在任何情况下都释放锁
lock.release()
if __name__ == "__main__":
threads = []
num_threads = 5
print(f"初始计数器值: {shared_counter}")
print(f"每个线程将计数器增加 {ITERATIONS} 次")
print(f"理论上的最终计数器值应为: {num_threads * ITERATIONS}")
for i in range(num_threads):
# 使用带锁的函数
thread = threading.Thread(target=increment_counter_safe)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
print(f"使用锁后,实际的最终计数器值: {shared_counter}")
print(f"与理论值的差额: {(num_threads * ITERATIONS) - shared_counter}")
# 也可以使用 with 语句来自动管理锁的获取和释放,更推荐
shared_counter = 0 # 重置计数器进行演示
threads = []
print("\n使用 'with lock' 语句的演示:")
def increment_counter_with_statement():
global shared_counter
for _ in range(ITERATIONS):
with lock: # 自动获取和释放锁
current_value = shared_counter
shared_counter = current_value + 1
for i in range(num_threads):
thread = threading.Thread(target=increment_counter_with_statement)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
print(f"使用 'with lock' 后,实际的最终计数器值: {shared_counter}")
print(f"与理论值的差额: {(num_threads * ITERATIONS) - shared_counter}")
使用锁后的代码解释:
我们来讨论 Python 中的“死锁”(Deadlock)现象,并举例说明。
什么是死锁 (Deadlock)?
死锁是并发编程中一种非常棘手的问题。当两个或多个线程(或进程)无限期地等待一个只有其他等待中的线程才能释放的资源时,就会发生死锁。这导致这些线程都无法继续执行,程序陷入停滞状态。
想象一下这个场景:
• 线程 A 持有资源 X,并等待资源 Y。
• 线程 B 持有资源 Y,并等待资源 X。
在这种情况下,线程 A 永远等不到资源 Y(因为线程 B 持有它并且在等待资源 X),线程 B 也永远等不到资源 X(因为线程 A 持有它并且在等待资源 Y)。双方都在互相等待对方先释放资源,从而形成死锁。
发生死锁的四个必要条件 (Coffman conditions):
只有当以下四个条件同时满足时,才可能发生死锁:
代码如下(示例):
下面是一个经典的死锁例子,涉及两个线程和两个锁:
import threading
import time
# 创建两个锁对象
lock_a = threading.Lock()
lock_b = threading.Lock()
def thread_1_logic():
print("线程 1: 尝试获取 lock_a...")
lock_a.acquire()
print("线程 1: 已获取 lock_a。")
print("线程 1: 休眠 1 秒,模拟一些工作...")
time.sleep(1) # 给线程 2 足够的时间去获取 lock_b
print("线程 1: 尝试获取 lock_b...")
lock_b.acquire() # 此处可能发生死锁
print("线程 1: 已获取 lock_b。")
# 释放锁 (理论上,如果能执行到这里)
lock_b.release()
lock_a.release()
print("线程 1: 已释放所有锁。")
def thread_2_logic():
print("线程 2: 尝试获取 lock_b...")
lock_b.acquire()
print("线程 2: 已获取 lock_b。")
print("线程 2: 休眠 1 秒,模拟一些工作...")
time.sleep(1) # 给线程 1 足够的时间去获取 lock_a
print("线程 2: 尝试获取 lock_a...")
lock_a.acquire() # 此处可能发生死锁
print("线程 2: 已获取 lock_a。")
# 释放锁 (理论上,如果能执行到这里)
lock_a.release()
lock_b.release()
print("线程 2: 已释放所有锁。")
if __name__ == "__main__":
print("启动死锁演示...")
t1 = threading.Thread(target=thread_1_logic)
t2 = threading.Thread(target=thread_2_logic)
t1.start()
t2.start()
t1.join() # 主线程等待 t1 结束
t2.join() # 主线程等待 t2 结束
print("死锁演示结束 (如果程序能正常结束的话)。")
代码解释与死锁的发生:
def thread_2_logic_fixed():
print("线程 2 (修复后): 尝试获取 lock_a...")
lock_a.acquire() # 先获取 lock_a
print("线程 2 (修复后): 已获取 lock_a。")
print("线程 2 (修复后): 休眠 1 秒...")
time.sleep(1)
print("线程 2 (修复后): 尝试获取 lock_b...")
lock_b.acquire() # 再获取 lock_b
print("线程 2 (修复后): 已获取 lock_b。")
lock_b.release()
lock_a.release()
print("线程 2 (修复后): 已释放所有锁。")
if lock_a.acquire(timeout=1): # 尝试获取锁,最多等待1秒
# 成功获取锁
try:
if lock_b.acquire(timeout=1):
# ...
else:
# 获取 lock_b 失败,可能需要释放 lock_a
print("线程 X: 获取 lock_b 超时")
finally:
lock_a.release()
else:
# 获取 lock_a 失败
print("线程 X: 获取 lock_a 超时")
这种方法会使代码更复杂,但可以避免无限期等待。
3. 死锁检测和恢复 (Deadlock Detection and Recovery): 一些更复杂的系统可能会有专门的机制来检测死锁的发生(例如,通过分析资源分配图)。一旦检测到死锁,系统可以采取措施来解除死锁,比如终止其中一个或多个线程,或者抢占资源(这违反了不可抢占条件,但有时是必要的)。Python 的 threading 模块本身不提供内置的死锁检测机制。
4. 减少锁的持有时间,避免嵌套锁: 尽量缩短持有锁的时间,并且尽可能避免在一个锁内部去获取另一个锁(即嵌套锁)。如果必须使用嵌套锁,锁排序就变得至关重要。
总结
死锁是并发编程中的一个严重问题,它会导致程序挂起。理解发生死锁的条件以及如何通过锁排序等策略来预防死锁,对于编写可靠的多线程 Python 程序至关重要。在设计并发逻辑时,务必仔细考虑资源获取的顺序。
python 线程同步使用,并举例?
显示思路
好的,Python 的 threading 模块提供了多种线程同步机制,用于解决多线程并发执行时可能出现的资源竞争、数据不一致以及执行顺序协调等问题。
为什么需要线程同步?
当多个线程共享数据或资源时,如果不对它们的访问进行控制,就可能发生:
示例 (解决竞争条件):
import threading
import time
shared_variable = 0
ITERATIONS = 100000
# 创建一个锁对象
lock = threading.Lock()
def increment_counter():
global shared_variable
for _ in range(ITERATIONS):
# 方式一:显式获取和释放
# lock.acquire()
# try:
# temp = shared_variable
# temp += 1
# shared_variable = temp
# finally:
# lock.release()
# 方式二:使用 with 语句 (推荐)
with lock:
temp = shared_variable
temp += 1
shared_variable = temp
def decrement_counter():
global shared_variable
for _ in range(ITERATIONS):
with lock:
temp = shared_variable
temp -= 1
shared_variable = temp
if __name__ == "__main__":
thread1 = threading.Thread(target=increment_counter)
thread2 = threading.Thread(target=decrement_counter)
start_time = time.time()
thread1.start()
thread2.start()
thread1.join()
thread2.join()
end_time = time.time()
print(f"最终共享变量的值: {shared_variable}") # 期望值为 0
print(f"耗时: {end_time - start_time:.4f} 秒")
说明: 如果没有 lock,shared_variable 的最终值将不确定。with lock: 确保了对 shared_variable 的读-改-写操作是原子的。
import threading
# 创建一个可重入锁
rlock = threading.RLock()
shared_resource = []
def recursive_function(depth):
if depth <= 0:
return
with rlock: # 同一个线程可以多次获取 rlock
print(f"线程 {threading.current_thread().name}: 获取 rlock, 深度 {depth}")
shared_resource.append(depth)
time.sleep(0.1)
recursive_function(depth - 1)
print(f"线程 {threading.current_thread().name}: 释放 rlock, 深度 {depth}")
if __name__ == "__main__":
thread_r = threading.Thread(target=recursive_function, args=(3,))
thread_r.start()
thread_r.join()
print(f"共享资源: {shared_resource}")
# 如果用普通的 Lock,第二次 acquire 就会死锁
import threading
import time
event = threading.Event()
def worker_task():
print("工作线程: 正在处理一些事情...")
time.sleep(3) # 模拟耗时操作
print("工作线程: 处理完毕,设置事件!")
event.set() # 发出事件信号
def listener_task():
print("监听线程: 等待事件...")
event.wait() # 阻塞直到 event.set() 被调用
print("监听线程: 收到事件信号,继续执行!")
if __name__ == "__main__":
t_worker = threading.Thread(target=worker_task)
t_listener = threading.Thread(target=listener_task)
t_listener.start() # 先启动监听者
time.sleep(0.1) # 确保监听者先进入 wait 状态
t_worker.start()
t_worker.join()
t_listener.join()
print("所有任务完成。")
import threading
import time
import random
condition = threading.Condition() # 默认会创建一个 Lock
items = []
MAX_ITEMS = 5
def producer():
global items
for i in range(10):
with condition: # 获取关联的锁
while len(items) == MAX_ITEMS:
print("生产者: 缓冲区已满,等待...")
condition.wait() # 等待,释放锁;被唤醒后重新获取锁
item = f"物品-{i}"
items.append(item)
print(f"生产者: 生产了 {item} (当前数量: {len(items)})")
condition.notify() # 通知一个等待的消费者
time.sleep(random.uniform(0.1, 0.5))
def consumer():
global items
for i in range(10):
with condition:
while not items:
print("消费者: 缓冲区为空,等待...")
condition.wait()
item = items.pop(0)
print(f"消费者: 消费了 {item} (剩余数量: {len(items)})")
condition.notify() # 通知可能在等待的生产者(如果缓冲区之前是满的)
time.sleep(random.uniform(0.1, 0.8))
if __name__ == "__main__":
t_prod = threading.Thread(target=producer)
t_cons = threading.Thread(target=consumer)
t_prod.start()
t_cons.start()
t_prod.join()
t_cons.join()
print("生产消费完成。")
import threading
import time
import random
# 创建一个信号量,允许最多3个线程同时访问资源
semaphore = threading.BoundedSemaphore(3) # 使用 BoundedSemaphore 更安全
def access_resource(thread_id):
print(f"线程 {thread_id}: 尝试访问资源...")
with semaphore: # acquire() 和 release() 自动管理
print(f"线程 {thread_id}: 已获取信号量,正在访问资源...")
time.sleep(random.uniform(1, 3)) # 模拟资源使用
print(f"线程 {thread_id}: 访问完毕,释放信号量。")
if __name__ == "__main__":
threads = []
for i in range(7): # 创建7个线程,但只有3个能同时访问
thread = threading.Thread(target=access_resource, args=(i,))
threads.append(thread)
thread.start()
time.sleep(0.1) # 稍微错开启动,方便观察
for thread in threads:
thread.join()
print("所有线程任务完成。")
import threading
import time
import random
PARTIES = 3
barrier = threading.Barrier(PARTIES, timeout=10) # 3个线程参与,超时10秒
def race_participant(participant_id):
print(f"选手 {participant_id}: 准备就绪...")
time.sleep(random.uniform(0.5, 2)) # 模拟准备时间
try:
print(f"选手 {participant_id}: 到达起跑线,等待发令...")
index = barrier.wait() # 等待其他选手
print(f"选手 {participant_id} (序号 {index}): 开始跑!")
except threading.BrokenBarrierError:
print(f"选手 {participant_id}: 比赛取消或超时!")
except threading.TimeoutError: # Python 3.2+ for Barrier timeout in wait
print(f"选手 {participant_id}: 等待发令超时!")
if __name__ == "__main__":
threads = []
for i in range(PARTIES):
thread = threading.Thread(target=race_participant, args=(i,))
threads.append(thread)
thread.start()
# 如果想演示屏障损坏,可以在某个线程调用 wait 之前调用 barrier.abort()
# time.sleep(1)
# if random.choice([True, False]):
# print("裁判:出现意外,比赛中止!")
# barrier.abort()
for thread in threads:
thread.join()
print("比赛结束。")
# 演示 reset
print("\n--- 准备第二轮比赛 ---")
barrier.reset() # 重置屏障以便复用
threads_round2 = []
for i in range(PARTIES):
thread = threading.Thread(target=race_participant, args=(i + PARTIES,))
threads_round2.append(thread)
thread.start()
for thread in threads_round2:
thread.join()
print("第二轮比赛结束。")
选择哪种同步原语取决于具体的同步需求:
• 互斥访问共享资源: Lock 或 RLock。
• 简单的信号通知: Event。
• 复杂的条件等待与通知: Condition。
• 控制对资源的并发访问数量: Semaphore。
• 多线程集合点同步: Barrier。
合理使用这些同步机制是编写健壮、高效的多线程 Python程序的关键。同时,也要注意避免过度同步导致性能下降,以及不当使用同步原语可能引入的死锁问题。
另外在等待事件中Don’t use ''for loop and sleep"for wait…
感谢大家的点赞收藏转发和关注,谢谢你。