Python 中 Condition、Event、Semaphore 有什么区别?
简化版
Event 适合做开关信号,一个线程通知其他线程可以继续;Condition 适合等待某个条件成立,常用于生产者消费者;Semaphore 用来限制同时访问某类资源的线程数量。它们都属于线程同步工具,但解决的问题不一样。
详细版
三者对比:
| 工具 | 核心语义 | 典型场景 |
|---|---|---|
Event | 一个布尔信号,set 后等待线程继续 | 启动信号、停止信号、配置加载完成 |
Condition | 等待条件变化,并配合锁保护状态 | 生产者消费者、等待队列非空 |
Semaphore | 计数器,限制并发访问数量 | 限制连接数、下载并发数、资源池 |
Event 示例:
ready = threading.Event()
def worker():
ready.wait()
do_work()
ready.set()
Semaphore 示例:
sem = threading.Semaphore(3)
def task():
with sem:
access_limited_resource()
Condition 示例:
condition = threading.Condition()
items = []
def consumer():
with condition:
while not items:
condition.wait()
item = items.pop()
面试重点:Event 是信号,Condition 是条件等待,Semaphore 是并发数量控制。
完整版教学
一、同步工具解决的不是同一个问题
很多同学把这些类都叫“锁”,但它们的抽象不同:
- 锁解决“同一时刻谁能进入临界区”;
- Event 解决“某个信号发生了吗”;
- Condition 解决“某个受保护条件成立了吗”;
- Semaphore 解决“最多允许几个线程同时进入”。
理解它们的语义,比背方法名更重要。
二、Event 像一个开关
Event 内部维护一个标志位。标志位未设置时,wait() 会阻塞;调用 set() 后,等待的线程会被唤醒。
import threading
ready = threading.Event()
def worker():
print("waiting")
ready.wait()
print("running")
threading.Thread(target=worker).start()
ready.set()
它适合一次性通知,比如“配置加载完成”“开始执行”“请求停止”。
如果要重复开关,可以用 clear() 重置,但复杂状态变化通常更适合 Condition。
三、Condition 要和状态一起看
Condition 通常和某个共享状态绑定。消费者等待队列非空:
condition = threading.Condition()
items = []
def producer(item):
with condition:
items.append(item)
condition.notify()
def consumer():
with condition:
while not items:
condition.wait()
return items.pop(0)
为什么要用 while not items,而不是 if not items?因为线程被唤醒后,条件不一定仍然成立。可能是虚假唤醒,也可能是其他消费者先拿走了数据。正确姿势是被唤醒后重新检查条件。
四、Semaphore 控制并发额度
信号量内部有一个计数器。进入时减少,离开时增加。计数为 0 时,后续线程等待。
sem = threading.Semaphore(5)
def download(url):
with sem:
return fetch(url)
它适合限制资源访问:
- 最多 5 个并发下载;
- 最多 10 个数据库连接;
- 最多 3 个任务同时调用第三方 API。
BoundedSemaphore 还会检查释放次数是否超过初始值,有助于发现重复释放问题。
五、不要把同步工具当业务队列乱用
如果你的需求是“多个生产者提交任务,多个消费者消费任务”,通常直接用 queue.Queue 更好。它已经内置了线程安全、阻塞获取、任务完成通知等能力。
Condition 更适合学习和实现底层协调逻辑,或者处理自定义条件。工程里能用更高层抽象时,不要手写复杂同步。
六、常见误区与追问
| 工具 | 解决的问题 | 典型面试判断 |
|---|---|---|
Event | 一次性或阶段性通知 | 多个线程等同一个“开关” |
Condition | 状态变化等待 | 必须配合共享状态和锁 |
Semaphore | 并发额度控制 | 限制同时进入临界资源的线程数 |
Queue | 任务传递和生产消费 | 比手写条件变量更适合队列场景 |
- 误区:Event、Condition、Semaphore 都是锁的不同名字。 它们都属于同步工具,但语义不同:
Event做通知,Condition等状态,Semaphore控制数量。 - 误区:Condition.wait() 被唤醒就说明条件一定成立。 等待可能被虚假唤醒,也可能被其他线程先改掉状态,所以应放在
while循环里重新检查条件。 - 误区:Semaphore 只要 release 就不会出错。 普通
Semaphore可能被多释放导致额度变大;需要防止多释放时可考虑BoundedSemaphore。 - 追问:为什么 Condition 必须和锁一起使用? 因为检查共享状态和进入等待必须是同一段受保护逻辑,否则会丢通知或读到竞争状态。
- 追问:Event.clear() 有什么风险? 清除后后续线程会重新阻塞,如果清除时机和业务阶段没设计好,可能让已经应该继续的线程再次卡住。
- 追问:什么时候不用这些工具而用 Queue? 当问题本质是“任务从生产者传给消费者”时,
Queue内置阻塞、线程安全和完成确认,比自己拼条件变量更稳。
记忆钩子:Event 是开关,Condition 是“等条件变真”,Semaphore 是“发固定数量的入场券”。
七、加强记忆
记住三个画面:Event 是开关,负责通知“可以了”;Condition 是门口的规则,醒来还要检查条件;Semaphore 是门票,控制最多几个人同时进去。同步工具要按语义选,不要把所有问题都写成一把锁。