title: 条件变量
date: 2024-06-28 19:00:00
categories:
- C++
- Multi-threaded
tags: QAQ #
条件变量
条件变量condition variable
是C++11引入的一种同步原语,用于实现线程之间的等待和唤醒机制。它通常与互斥锁(mutex)结合使用,以实现复杂的线程同步和通信。
条件变量允许一个或多个线程等待某个条件满足后再继续执行。它提供了一种线程间的通知机制,使得一个线程可以等待另一个线程发出的信号。
主要成员函数
-
wait:
-
void wait(std::unique_lock<std::mutex>& lock,Predicate pred);
-
当前线程在获取到锁后调用
wait
,会释放锁并进入等待状态,直到被其他线程唤醒。 -
wait
导致当前线程阻塞,直到通知条件变量或发生虚假唤醒。可以选择提供 pred 来检测虚假唤醒。 -
例子:
std::mutex mtx; std::condition_variable cv; bool ready = false; void wait_for_ready() { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{ return ready;}); // 线程被唤醒后继续执行 }
-
-
notify_one:
-
void notify_one();
-
唤醒一个等待该条件变量的线程。
-
例子:
void set_ready() { std::unique_lock<std::mutex> lock(mtx); ready = true; cv.notify_one(); }
-
-
notify_all:
-
void notify_all();
-
唤醒所有等待该条件变量的线程。
-
例子:
void set_ready_for_all() { std::unique_lock<std::mutex> lock(mtx); ready = true; cv.notify_all(); }
使用场景
条件变量常用于生产者-消费者模型、事件通知等场景。以下是一个简单的生产者-消费者模型示例:
#include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <queue> std::mutex mtx; std::condition_variable cv; std::queue<int> dataQueue; bool done = false; void producer() { for (int i = 0; i < 10; ++i) { std::unique_lock<std::mutex> lock(mtx); dataQueue.push(i); cv.notify_one(); } std::unique_lock<std::mutex> lock(mtx); done = true; cv.notify_all(); } void consumer() { while (true) { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{ return !dataQueue.empty() || done; }); while (!dataQueue.empty()) { int data = dataQueue.front(); dataQueue.pop(); std::cout << "Consumed: " << data << std::endl; } if (done) break; } } int main() { std::thread prod(producer); std::thread cons(consumer); prod.join(); cons.join(); return 0; }
-
这是一个简单的生产者消费者问题,消费者中通过cv.wait(lock)来阻塞当前线程,表示当前线程阻塞等待当前条件变量满足条件,这里wait需要一个lock参数,wait
函数不仅需要等待条件变量,还需要在等待期间释放互斥锁,并在被唤醒后重新获取锁。
-
释放锁以避免死锁
当一个线程调用
wait
时,它会进入等待状态。如果在等待期间不释放互斥锁,其他线程将无法获取锁,从而无法改变条件变量的状态,这会导致死锁。因此,wait
函数在进入等待状态前会释放互斥锁。 -
重新获取锁以确保线程安全
当条件变量被唤醒时,
wait
函数会重新获取互斥锁,以确保在检查条件变量状态时,线程对共享资源的访问是安全的。 -
使用std::unique_lock的灵活性
std::unique_lock
提供了比std::lock_guard
更灵活的锁管理功能。它允许在锁定和解锁之间进行更多的控制,例如延迟加锁、提前解锁等。这种灵活性对于条件变量的使用非常重要,因为wait
函数需要在内部释放和重新获取锁。
注意事项
-
避免虚假唤醒:
什么是虚假唤醒?
-
条件变量可能会在没有满足条件的情况下被唤醒,这种情况称为虚假唤醒。为了确保线程在条件满足时才继续执行,可以传入一个谓词来反复检查条件。
std::mutex mtx; std::condition_variable cv; bool ready = false; void wait_for_ready() { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, []{ return ready; }); // 传入lambda表达式检查条件 std::cout << "Thread is ready!" << std::endl; }
当这个线程被唤醒时,会先进行判断这个谓词是否为true来检查条件是否满足,如果为true则继续执行线程,否则将继续wait
使用
wait
时,通常需要在循环中检查条件,以避免虚假唤醒。或者直接指定wait的第二个参数,在这个谓词中进行条件判断可以省去循环代码,例如:
-
-
锁的使用:
wait
函数需要传入一个std::unique_lock<std::mutex>
对象,而不是std::lock_guard
,因为wait
会在内部释放并重新获取锁。 -
通知顺序:
notify_one
和notify_all
应在持有锁的情况下调用,以确保条件变量的状态与互斥锁的状态一致。
其他成员
wait_for | 阻塞当前线程,直到条件变量被唤醒或在指定的超时持续时间之后 (public member function) (公共成员函数) |
---|---|
wait_until | 阻塞当前线程,直到条件变量被唤醒或直到达到指定的时间点 (public member function) (公共成员函数) |