1️⃣ threading.Event 基础回顾
import threading
e = threading.Event()
# 线程 A
e.wait() # 阻塞直到 e.set() 被调用
# 线程 B
e.set() # 触发 event,所有 wait 的线程会继续执行
Event 本质上是一个线程安全的 标志位。
wait() 会阻塞当前线程直到标志位为 True。
set() 会把标志位置为 True,所有阻塞的线程会立即返回。
clear() 会把标志位置为 False。
2️⃣ 死锁发生的典型场景
死锁通常出现于下面几种情况:
场景 1:事件被“遗忘触发”
import threading, time
event = threading.Event()
def worker():
print("等待事件...")
event.wait() # 阻塞
print("事件触发,继续执行")
threading.Thread(target=worker).start()
time.sleep(2)
# event.set() 忘了调用,线程一直阻塞
这里不是严格的死锁,但表现为线程长期阻塞。
解决方法:加超时 wait(timeout=5) 或保证 set() 一定会调用。
场景 2:事件触发顺序依赖导致死锁
假设有两个线程依赖对方触发事件:
e1 = threading.Event()
e2 = threading.Event()
def t1():
print("T1 等待 e2")
e2.wait()
print("T1 设置 e1")
e1.set()
def t2():
print("T2 等待 e1")
e1.wait()
print("T2 设置 e2")
e2.set()
threading.Thread(target=t1).start()
threading.Thread(target=t2).start()
分析:
T1 等待 e2,T2 等待 e1。
两个事件都未被触发 → 死锁。
✅ 解决方法:
避免循环依赖。
引入超时:
e1.wait(timeout=5)
或使用单个事件替代两个事件,简化依赖。
场景 3:在持有锁的情况下调用 wait()
lock = threading.Lock()
event = threading.Event()
def worker():
with lock:
event.wait() # 阻塞
如果主线程也需要获取同一个 lock 来 set() 事件:
with lock:
event.set()
结果:死锁,因为 worker 持有锁阻塞,主线程无法获得锁来 set()。
✅ 解决方法:
不要在持有锁时调用 wait()。
或者使用 Condition 结合 with/notify(),更适合这种场景。
场景 4:多线程复杂依赖
如果有多个线程和多个事件交错触发,容易出现死锁。关键是 事件触发顺序无法保证:
# T1: 等待 e1
# T2: 等待 e2
# T3: 等待 e3
# ...
# Tn: 设置某些 event
# 如果循环依赖 → 所有线程都卡住
✅ 解决方法:
使用 事件图 检查依赖。
或使用 Queue + 消息机制 替代 Event,更容易控制顺序。
3️⃣ 避免死锁的通用技巧
避免循环依赖
任何两个线程互相等待对方事件 → 死锁。
尽量减少持有锁时调用 wait
Event 本身是线程安全的,不需要外部锁。
加超时 wait(timeout=...)
即使发生逻辑错误,也能返回,便于调试。
使用单个 Event 控制状态
如果多个事件容易交错依赖,考虑用 标志位 + Event 或 Queue。
日志调试
给每个 wait() 和 set() 打日志,方便看触发顺序。
⚡ 实战示例:安全使用 Event
import threading
import time
event = threading.Event()
def worker():
print("Worker 开始等待...")
if event.wait(timeout=5): # 设置超时
print("Worker 继续执行")
else:
print("Worker 超时退出")
t = threading.Thread(target=worker)
t.start()
time.sleep(2)
event.set()
即使 set() 没被调用,线程 5 秒后会自动退出。
避免无限阻塞 → 间接避免死锁。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70050070/viewspace-3171726/ ,如需转载,请注明出处,否则将追究法律责任。