【C++】条件变量condition_variable(1)
互斥量(mutex
)和条件变量(condition_variable
)在多线程编程中通常是配合使用的,它们解决的是不同层面的问题。简单来说:
-
互斥量(
mutex
) 用于 互斥访问 共享资源,保证同一时间只有一个线程可以访问临界区,防止数据竞争。 -
条件变量(
condition_variable
) 解决的是 线程同步 的问题,允许线程在特定条件满足前进入等待状态,并在条件满足时被通知唤醒。
为啥有了互斥量还需要 condition_variable
?
互斥量本身只能防止多个线程同时访问共享资源,但不能用于线程间的等待和通知机制。如果线程需要等待某个条件成立才能继续执行,仅靠互斥量会导致忙等待(不断轮询检查条件),这样会浪费 CPU 资源。而 condition_variable
可以让线程高效等待,避免资源浪费。
具体场景:
1. 没有 condition_variable
的情况(忙等待)
std::mutex mtx;
bool ready = false; // 共享资源的状态
void worker() {
while (true) {
mtx.lock();
if (ready) {
std::cout << "Processing data..." << std::endl;
mtx.unlock();
break;
}
mtx.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 轮询等待
}
}
这里,worker
线程必须不断轮询 ready
变量是否变为 true
,即使 ready
很长时间都不变,线程仍然会定期尝试获取互斥锁,导致 CPU 资源浪费。
2. 使用 condition_variable
(高效等待)
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; }); // 线程会在这里等待,直到 ready 变为 true
std::cout << "Processing data..." << std::endl;
}
void producer() {
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟数据准备时间
std::lock_guard<std::mutex> lock(mtx);
ready = true;
cv.notify_one(); // 唤醒等待的线程
}
在这个版本中,worker
线程会在 cv.wait()
处 阻塞,直到 producer
线程调用 cv.notify_one()
,线程才会被唤醒并继续执行。这种方式避免了 CPU 资源的浪费,提升了效率。
总结:
- 互斥量 只负责保护数据的访问,不能用来通知其他线程状态的变化。
condition_variable
让线程可以高效等待某个条件,避免忙等待带来的性能浪费。- 它们通常配合使用,
condition_variable
依赖mutex
来保护共享数据的状态。
因此,即使有了互斥量,在需要线程同步的场景下,我们仍然需要 condition_variable
来高效地协调线程的执行顺序。
想象一个餐厅点餐的场景,我们用它来类比 mutex
和 condition_variable
的作用。
场景描述
在一个餐厅里:
- 顾客点餐后需要等待厨师做好饭。
- 厨师在顾客点餐之前是不会做饭的。
- 顾客不能频繁去后厨查看饭是不是好了,否则会很麻烦。
- 当饭做好后,厨师会通知顾客来取饭。
不用 condition_variable
,仅用 mutex
(忙等待的情况)
如果没有 condition_variable
,顾客会不断去后厨查看饭是否好了:
std::mutex mtx;
bool food_ready = false; // 饭是否做好
void customer() {
while (true) {
mtx.lock();
if (food_ready) {
std::cout << "顾客:拿到饭,开始吃!" << std::endl;
mtx.unlock();
break;
}
mtx.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 轮询等待
std::cout << "顾客:饭好了没?" << std::endl;
}
}
void chef() {
std::this_thread::sleep_for(std::chrono::seconds(3)); // 模拟做饭时间
mtx.lock();
food_ready = true;
std::cout << "厨师:饭做好了!" << std::endl;
mtx.unlock();
}
问题:
- 顾客不停地轮询后厨(忙等待),就像线程不断检查共享变量
food_ready
,浪费 CPU 资源。 - 这个过程即使饭还没好,顾客仍然会每隔一段时间就去“打扰”后厨(检查
food_ready
变量)。
使用 condition_variable
(高效等待)
如果用 condition_variable
,顾客可以安心等厨师通知,而不是不停地去检查。
std::mutex mtx;
std::condition_variable cv;
bool food_ready = false;
void customer() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return food_ready; }); // 等待厨师通知
std::cout << "顾客:拿到饭,开始吃!" << std::endl;
}
void chef() {
std::this_thread::sleep_for(std::chrono::seconds(3)); // 模拟做饭时间
std::lock_guard<std::mutex> lock(mtx);
food_ready = true;
std::cout << "厨师:饭做好了!" << std::endl;
cv.notify_one(); // 通知顾客饭已经好了
}
改进点:
- 顾客不用一直去问饭好了没,而是安心等待,一旦厨师通知,顾客才去取饭。
- 这个机制避免了不必要的 CPU 轮询,就像
cv.wait()
让线程休眠,直到cv.notify_one()
叫醒它。
总结
方式 | 现实类比 | 编程效果 |
---|---|---|
只用 mutex | 顾客不停地去厨房问“饭好了没?” | 线程不断检查变量(忙等待,浪费 CPU) |
condition_variable + mutex | 顾客坐着等,厨师做好饭后叫他来取 | 线程高效等待,不浪费资源 |
所以,互斥量(mutex
)解决数据竞争问题,而 condition_variable
解决高效等待和线程同步的问题,它们是互补的!