Python中的并发编程:线程同步与条件变量(Condition)详解
字数 1433 2025-12-10 13:39:58
Python中的并发编程:线程同步与条件变量(Condition)详解
1. 问题引入:为什么要用条件变量?
想象一个经典场景:生产者-消费者问题。
- 生产者不断生产数据,放入共享队列。
- 消费者不断从队列取出数据消费。
- 如果队列空,消费者必须等待;如果队列满,生产者必须等待。
用简单的锁(Lock)可以保证队列操作线程安全,但无法实现“等待特定条件成立”的功能。
条件变量(Condition)就是为了解决这类“等待/通知”场景而设计的同步原语。
2. 条件变量的基本组成
Python的threading.Condition包含三个核心部分:
- 一个底层锁(可以是
RLock或用户提供的锁)。 - 一个等待池(
wait时线程进入等待状态)。 - 通知机制(
notify/notify_all唤醒等待线程)。
创建方式:
import threading
cond = threading.Condition() # 内部自动创建RLock
# 或使用已有锁
lock = threading.Lock()
cond = threading.Condition(lock)
3. 条件变量的核心方法
3.1 acquire() 和 release()
条件变量内部锁的获取/释放,与普通锁用法一致:
cond.acquire() # 获取内部锁
try:
# 操作共享资源
if not condition_met:
cond.wait() # 释放锁并等待
finally:
cond.release() # 释放锁
通常使用with语句简化:
with cond:
if not condition_met:
cond.wait()
3.2 wait(timeout=None)
- 作用:释放内部锁,将当前线程放入等待池,线程进入阻塞状态。
- 被唤醒后:重新获取锁(可能需与其他线程竞争),然后继续执行。
- 重要:调用
wait()前必须先获取锁,否则报错RuntimeError。
为什么先释放锁?
如果持有锁等待,其他线程无法修改条件,导致死锁。
3.3 notify(n=1) 和 notify_all()
notify():唤醒等待池中最多n个线程(FIFO顺序)。notify_all():唤醒所有等待线程。- 注意:调用这些方法前也必须先获取锁。
唤醒后发生了什么?
被唤醒的线程会从wait()返回,但需要重新竞争获取内部锁,获取成功后才能真正继续执行。
4. 生产者-消费者完整示例
import threading
import time
import random
queue = []
MAX_ITEMS = 5
condition = threading.Condition()
class Producer(threading.Thread):
def run(self):
global queue
while True:
with condition: # 获取锁
if len(queue) == MAX_ITEMS:
print("队列已满,生产者等待")
condition.wait() # 释放锁,等待
item = random.randint(1, 100)
queue.append(item)
print(f"生产: {item}, 队列长度: {len(queue)}")
condition.notify() # 唤醒一个消费者
time.sleep(random.random())
class Consumer(threading.Thread):
def run(self):
global queue
while True:
with condition:
if not queue:
print("队列为空,消费者等待")
condition.wait() # 释放锁,等待
item = queue.pop(0)
print(f"消费: {item}, 队列长度: {len(queue)}")
condition.notify() # 唤醒一个生产者
time.sleep(random.random() * 2)
# 启动线程
Producer().start()
Consumer().start()
执行流程:
- 消费者先运行,发现队列空,调用
wait()释放锁并等待。 - 生产者获取锁,生产数据,调用
notify()唤醒消费者。 - 消费者被唤醒,但需等待生产者退出
with块释放锁后,才能获取锁并从wait()返回。
5. 条件变量的常见陷阱
5.1 虚假唤醒(Spurious Wakeup)
操作系统可能无故唤醒等待线程,因此wait返回后必须重新检查条件:
with cond:
while not condition_met: # 用while而不是if
cond.wait()
# 条件满足,继续执行
5.2 通知丢失
如果先notify()后wait(),通知可能丢失。确保状态改变和通知在同一个锁内:
with cond:
condition_met = True
cond.notify() # 在释放锁前通知
5.3 死锁风险
嵌套使用多个条件变量时,需小心锁的获取顺序,避免循环等待。
6. 条件变量与threading.Event的区别
Event:一次性广播,适合简单开关场景(如线程启动信号)。Condition:可多次等待/通知,与共享状态变更紧密耦合。
7. 实际应用场景
- 线程池任务调度:工作线程等待任务队列非空。
- 资源池管理:数据库连接池中,线程等待可用连接。
- 并行计算屏障:多个线程同步到某个阶段后继续。
8. 总结要点
- 条件变量 = 锁 + 等待池 + 通知机制。
- 必须用锁保护共享状态的修改和检查。
- 永远在
while循环中调用wait(),防范虚假唤醒。 notify()和wait()必须在已获取锁的情况下调用。
通过条件变量,我们可以实现高效的线程间协作,避免忙等待(busy-waiting),提升并发程序性能。