← 返回题目列表

Python 中 Condition、Event、Semaphore 有什么区别?

高频 中等 第 5 / 27 题 更新于 2026/07/27
ConditionEventSemaphore线程同步

简化版

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 是门票,控制最多几个人同时进去。同步工具要按语义选,不要把所有问题都写成一把锁。