C++ 条件变量
互斥锁和条件变量
在多线程编程中,同步机制是保证程序正确性的关键。互斥锁(mutex)作为最基础的同步原语,能够有效保护临界区资源。但当遇到线程间协作的场景时,仅靠互斥锁就显得力不从心。
典型场景对比:
- 互斥锁适用场景:多线程修改共享资源(如线程安全的哈希表)
- 条件变量适用场景:线程依赖其他线程产生的数据(如生产者-消费者模型)
传统轮询方案(伪代码):
1 2 3 4 5 6 7 8 9 10
| while(true) { lock(mutex); if(condition) { unlock(mutex); break; } unlock(mutex); sleep(100); }
|
条件变量方案对比优势:
- 零等待开销:线程在条件不满足时主动休眠
- 精准唤醒:通过通知机制避免无效轮询
- 原子性操作:保证条件判断与等待的原子性
条件变量核心机制
五元协同模型
条件变量的高效运作依赖五个核心要素:
- 共享条件(condition):决定线程是否继续执行的状态判断
- 互斥锁(mutex):保护共享条件的访问
- 条件变量(cv):实现等待/通知的通信机制
- 等待线程:条件不满足时进入休眠
- 通知线程:修改条件后触发唤醒
标准接口详解
C++11提供两种条件变量实现:
std::condition_variable
:高性能标准实现
std::condition_variable_any
:支持任意锁类型
等待函数对比
函数 |
特性 |
超时控制 |
wait |
基础等待 |
无 |
wait_for |
相对时间等待 |
chrono::duration |
wait_until |
绝对时间等待 |
chrono::time_point |
唤醒函数对比
函数 |
唤醒范围 |
使用场景 |
notify_one |
单个线程 |
单消费者模型 |
notify_all |
所有线程 |
多消费者/广播场景 |
条件变量使用示例
通用代码模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| std::mutex mtx; std::condition_variable cv; bool condition = false;
{ std::unique_lock<std::mutex> lk(mtx); cv.wait(lk, []{ return condition; }); }
{ std::lock_guard<std::mutex> lk(mtx); condition = true; } cv.notify_one();
|
生产-消费模型实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #include <queue> #include <thread>
std::mutex mtx; std::condition_variable cv; std::queue<int> data_queue;
void producer() { for(int i=0; ;++i) { { std::lock_guard<std::mutex> lk(mtx); data_queue.push(i); } cv.notify_one(); std::this_thread::sleep_for(std::chrono::seconds(1)); } }
void consumer() { while(true) { std::unique_lock<std::mutex> lk(mtx); cv.wait(lk, []{ return !data_queue.empty(); }); int data = data_queue.front(); data_queue.pop(); lk.unlock(); process(data); } }
|
补充
4.1 互斥锁的必要性
条件变量必须与互斥锁配合使用的根本原因在于保证操作的原子性:
- Lost Wakeup防护:避免判断条件与进入等待之间的间隙导致通知丢失
- 数据一致性:确保共享条件的修改可见性
- 唤醒原子性:保证唤醒操作与条件修改的原子关系
操作时序图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| [Wait Thread] [Mutex] [Condition] [Notify Thread] | | | | |-- Lock ------->| | | |-- Check Cond --| | | |<<Cond False>> | | | |-- Wait --------|------------->| | | | Release | | | |<-------------| | | | |-- Lock ---------| | | |-- Modify Cond --| | | |-- Notify -------| |<-- Wake Up ----|--------------| | |-- Re-Lock ---->| | | |-- Check Cond --| | | |<<Cond True>>> | | |
|
虚假唤醒应对策略
虚假唤醒的根源来自:
防御性编程方案:
1 2 3 4 5 6 7
| while(!condition) { cv.wait(lock); }
cv.wait(lock, []{ return condition; });
|
性能优化实践
- 锁粒度控制:在
wait
返回后及时缩小锁作用域
- 通知优化:在非临界区执行
notify
调用
- 批量处理:合并多次操作为单次通知
- 自旋等待:对预期短期等待的场景采用混合策略
1 2 3 4 5 6 7 8 9 10
| void batch_update() { { std::lock_guard<std::mutex> lk(mtx); for(auto& item : data) item.update(); ready = true; } cv.notify_all(); }
|
进阶应用场景
读写锁模拟
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class ReadWriteLock { std::mutex mtx; std::condition_variable cv; int readers = 0; bool writing = false; public: void read_lock() { std::unique_lock<std::mutex> lk(mtx); cv.wait(lk, [&]{ return !writing; }); ++readers; } void write_lock() { std::unique_lock<std::mutex> lk(mtx); cv.wait(lk, [&]{ return !writing && readers == 0; }); writing = true; } };
|
屏障同步实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class ThreadBarrier { std::mutex mtx; std::condition_variable cv; int count; int generation = 0; public: explicit ThreadBarrier(int n) : count(n) {} void wait() { std::unique_lock<std::mutex> lk(mtx); int gen = generation; if(--count == 0) { ++generation; count = initial; cv.notify_all(); } else { cv.wait(lk, [&]{ return gen != generation; }); } } };
|
参考 :
std::condition_variable - cppreference.com
condition_variable - C++ Reference - Cplusplus.com
Why does pthread_cond_wait have spurious wakeups?
Calling pthread_cond_signal without locking mutex