操作系统中的线程同步:条件变量(Condition Variable)
字数 1016 2025-11-04 20:48:21
操作系统中的线程同步:条件变量(Condition Variable)
一、问题描述
条件变量是操作系统线程同步的重要机制,用于解决线程间等待特定条件成立的场景。当某个条件不满足时,线程可以主动等待在该条件变量上;当其他线程改变了条件并使其成立时,可以唤醒等待的线程继续执行。它通常与互斥锁配合使用,确保对共享条件的检查与修改是原子操作。
二、核心概念解析
-
为什么需要条件变量?
- 互斥锁只能解决临界区的互斥访问,但无法解决"等待条件成立"的问题
- 示例:消费者线程需要等待队列非空才能消费,单纯用互斥锁会导致忙等待(不断循环检查),浪费CPU资源
-
条件变量的核心操作
- wait(lock): 释放锁并进入等待,被唤醒后重新获取锁
- signal()/notify(): 唤醒一个等待线程
- broadcast()/notify_all(): 唤醒所有等待线程
三、条件变量的标准使用模式
以生产者-消费者问题为例(单条件变量简化版):
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
Queue buffer; // 共享缓冲区
// 生产者线程
void producer() {
pthread_mutex_lock(&lock);
// 生产数据并放入缓冲区
buffer.push(item);
// 通知消费者
pthread_cond_signal(&cond);
pthread_mutex_unlock(&lock);
}
// 消费者线程
void consumer() {
pthread_mutex_lock(&lock);
while (buffer.empty()) { // 必须用while循环检查
pthread_cond_wait(&cond, &lock); // 原子操作:释放锁+等待
}
// 消费数据
item = buffer.pop();
pthread_mutex_unlock(&lock);
}
四、关键细节与原理分析
-
为什么wait()需要与互斥锁配合?
- 保证检查条件(如buffer.empty())和进入等待是原子操作
- 防止这种竞态条件:消费者检查条件为真→准备等待时,生产者修改了条件并发送信号(此时消费者还未等待,信号丢失)
-
为什么用while循环而不是if判断条件?
- 虚假唤醒:某些系统会无缘无故唤醒线程(如信号中断、多个消费者时被其他消费者抢走资源)
- 广播唤醒:被broadcast()唤醒后需要重新检查条件
- 因此被唤醒后必须重新验证条件是否真正满足
-
wait()操作的原子性原理
- 步骤1:释放互斥锁(让其他线程能修改条件)
- 步骤2:线程进入等待队列阻塞
- 步骤3:被唤醒后重新获取互斥锁
- 这三个步骤是原子操作,防止信号丢失
五、条件变量的实际应用场景
- 线程池任务调度:工作线程等待任务队列非空
- 读写锁实现:写线程等待所有读线程完成
- 屏障同步:多个线程等待彼此到达同步点
- 有限状态机:线程等待状态转换
六、常见错误与最佳实践
- 错误示例:未加锁直接检查条件(竞态条件)
- 错误示例:用if代替while检查条件(虚假唤醒问题)
- 最佳实践:总是使用"加锁→while检查条件→wait→解锁"模式
- 信号时机:在修改条件后发送信号,通常放在临界区内(持有锁时)
通过条件变量的正确使用,可以实现高效的线程同步,避免忙等待带来的CPU浪费,是构建高性能并发程序的基础工具。