C++ 并发专题 - 条件变量的使用
一:概述:
在 C++ 中,条件变量(std::condition_variable
)是一种用于线程间同步的机制,主要用于在多线程环境中让一个线程等待某个条件满足后再继续执行。条件变量通常配合互斥锁(std::mutex
)使用,保证了在访问共享数据时不会发生竞态条件。
二:条件变量的用途:
条件变量用于在某个线程等待另一个线程满足特定条件时进行同步。这通常用于以下几种情况:
- 生产者-消费者问题:当缓冲区为空时,消费者线程等待生产者线程生产数据;当缓冲区满时,生产者线程等待消费者线程消费数据。
- 线程池:工作线程等待任务队列中有任务可处理。
- 任务调度:线程等待其他线程完成某些前置任务。
三:条件变量的工作原理:
- 等待条件:线程可以在条件变量上等待,直到某个条件成立(例如,某个标志被设置)。
- 通知条件:当某个线程修改共享数据并满足条件时,它可以通过条件变量通知等待的线程,通常使用
notify_one()
或notify_all()
方法。
四:核心方法:
wait
:让当前线程等待,直到满足指定条件。在调用wait
时,条件变量会自动释放与之关联的互斥锁,等待条件满足后再重新获取锁。notify_one
:唤醒一个在条件变量上等待的线程。如果没有线程在等待,它什么也不做。notify_all
:唤醒所有在条件变量上等待的线程。
五:条件变量的使用注意事项:
- 避免虚假唤醒:条件变量的
wait
方法会有可能被虚假唤醒(即条件未改变时线程被唤醒)。因此,通常需要在wait
语句中使用一个循环来检查条件:while (!condition) { cv.wait(lock); }
- 锁的管理:
wait
会释放互斥锁并进入休眠状态,直到被通知并且重新获得锁。使用std::unique_lock
管理锁是推荐的做法,因为它支持锁的自动管理。 notify_one
vsnotify_all
:notify_one()
只会唤醒一个线程,而notify_all()
会唤醒所有等待的线程。根据需要选择使用哪一个方法,通常只有一个线程需要继续时使用notify_one()
,而如果有多个线程依赖于同一条件时,则可能需要使用notify_all()
。
六:示例
#include <condition_variable>
#include <iostream>
#include <thread>
#include <mutex>
bool dataReady = false;
std::mutex mutex_;
std::condition_variable condVar1;
std::condition_variable condVar2;
int counter = 0;
int COUNTLIMIT = 50;
void setTrue()
{
while (counter <= COUNTLIMIT)
{
std::unique_lock<std::mutex> lck(mutex_);
condVar1.wait(lck, [] {return dataReady == false; });
dataReady = true;
++counter;
std::cout << dataReady << '\n';
condVar2.notify_one();
}
}
void setFalse()
{
while (counter <= COUNTLIMIT) // 循环直到 counter 达到 COUNTLIMIT
{
std::unique_lock<std::mutex> lck(mutex_); // 获取互斥锁,保护共享数据
condVar2.wait(lck, [] {return dataReady == true; }); // 等待条件变量,直到 dataReady 为 true
dataReady = false; // 修改 dataReady 为 false
std::cout << dataReady << '\n'; // 输出 dataReady 的值(即 false)
condVar1.notify_one(); // 唤醒另一个线程,通知它继续执行
}
}
int main()
{
std::cout << std::boolalpha << '\n';
std::cout << "Begin: " << dataReady << '\n';
std::thread t1(setTrue);
std::thread t2(setFalse);
t1.join();
t2.join();
dataReady = false;
std::cout << "End: " << dataReady << '\n';
std::cout << '\n';
return 0;
}