一 condition_variable
- condition_variable 类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable 。
- 常用于生产-消费场景。生产线程通过 notify_one 或者 notify_all 通知消费线程,消费线程有任务则消费,无任务时通过 wait、wait_for 或 wait_until 进行等待。
二 定义
-
定义
// 头文件 <condition_variable> class condition_variable; (C++11 起)
-
构造
condition_variable(); (1)(C++11 起) condition_variable(const condition_variable&) = delete; (2)(C++11 起)
- 不能复制构造。
-
operator= 已删除
-
析构
~condition_variable(); (C++11 起)
- 若已通知所有线程,调用析构函数才是安全的。
三 通知
- notify_one
-
通知一个等待的线程
void notify_one() noexcept; (C++11 起)
- notify_all
-
通知所有等待的线程
void notify_all() noexcept; (C++11 起)
四 等待
- wait
-
阻塞当前线程,直到条件变量被唤醒
void wait( std::unique_lock<std::mutex>& lock ); (1)(C++11 起) template< class Predicate > void wait( std::unique_lock<std::mutex>& lock, Predicate pred ); (2)(C++11 起)
-
其中 (2) 相当于:
while (!pred()) { wait(lock); }
- wait_until
-
阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点。
template< class Clock, class Duration > std::cv_status wait_until( std::unique_lock<std::mutex>& lock, const std::chrono::time_point<Clock, Duration>& timeout_time ); (1)(C++11 起) template< class Clock, class Duration, class Pred > bool wait_until( std::unique_lock<std::mutex>& lock, const std::chrono::time_point<Clock, Duration>& timeout_time, Pred pred ); (2)(C++11 起)
-
其中 (2) 相当于:
while (!pred()) { if (wait_until(lock, timeout_time) == std::cv_status::timeout) { return pred(); } } return true;
-
time_point 参见此前文章 C++11 time_point。
- wait_for
-
阻塞当前线程,直到条件变量被唤醒,或到指定时限时长后
template< class Rep, class Period > std::cv_status wait_for( std::unique_lock<std::mutex>& lock, const std::chrono::duration<Rep, Period>& rel_time); (1)(C++11 起) template< class Rep, class Period, class Predicate > bool wait_for( std::unique_lock<std::mutex>& lock, const std::chrono::duration<Rep, Period>& rel_time, Predicate pred); (2)(C++11 起)
-
其中 (2) 相当于:
return wait_until(lock, std::chrono::steady_clock::now() + rel_time, std::move(pred));
-
duration 参见此前文章 C++11 std::duration。
五 例子
- demo1 (cppreference)
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
void worker_thread() {
// Wait until main() sends data
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [] { return ready; });
// after the wait, we own the lock.
std::cout << "Worker thread is processing data\n";
data += " after processing";
// Send data back to main()
processed = true;
std::cout << "Worker thread signals data processing completed\n";
// Manual unlocking is done before notifying, to avoid waking up
// the waiting thread only to block again (see notify_one for details)
lk.unlock();
cv.notify_one();
}
int main() {
std::thread worker(worker_thread);
data = "Example data";
// send data to the worker thread
{
std::lock_guard<std::mutex> lk(m);
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
// wait for the worker
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [] { return processed; });
}
std::cout << "Back in main(), data = " << data << '\n';
worker.join();
std::cin.get();
return 0;
}
- 结果:
main() signals data ready for processing
Worker thread is processing data
Worker thread signals data processing completed
Back in main(), data = Example data after processing
- demo2
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <vector>
#include <atomic>
std::mutex task_mutex;
std::vector<std::function<void(int)>> task_vc;
std::mutex cv_mutex;
std::condition_variable cv;
std::mutex cout_mutex; // 避免输出信息错乱
std::atomic<int> exit_flag = 0;
using namespace std::chrono_literals;
void ProcessLoop( int num) {
while (!exit_flag) {
{
// 消费
std::lock_guard<std::mutex> guard(task_mutex);
if (task_vc.size() > 0) {
auto task = task_vc.front();
task_vc.erase(task_vc.begin());
task(num);
}
}
{
{ // 仅展示用,消费一次后强制等待100ms
std::lock_guard<std::mutex> guard(cout_mutex);
std::cout << "thread " << num
<< " : wait for 100ms." << std::endl;
}
std::unique_lock<std::mutex> lock(cv_mutex);
cv.wait_for(lock, 100ms);
}
}
}
int main() {
std::vector<std::thread> td_vc;
for (int i = 0; i < 4; i++) {
td_vc.push_back(std::thread(ProcessLoop, i));
}
for (int i = 0; i < 10; i++) {
std::lock_guard<std::mutex> guard(task_mutex);
// 生产
task_vc.push_back([](int num) {
std::lock_guard<std::mutex> guard(cout_mutex);
std::cout << "thread " << num << " : hello" << std::endl;
});
// 通知消费
std::unique_lock<std::mutex> lock(cv_mutex);
cv.notify_one();
}
std::this_thread::sleep_for(500ms); // 等待消费线程执行完毕
exit_flag.exchange(1);
std::cin.get();
return 0;
}
- 结果
thread 0 : wait for 100ms.
thread 1 : wait for 100ms.
thread 2 : wait for 100ms.
thread 1 : hello
thread 1 : wait for 100ms.
thread 0 : hello
thread 0 : wait for 100ms.
thread 2 : hello
thread 2 : wait for 100ms.
thread 3 : hello
thread 3 : wait for 100ms.
thread 3 : hello
thread 3 : wait for 100ms.
thread 2 : hello
thread 2 : wait for 100ms.
thread 1 : hello
thread 1 : wait for 100ms.
thread 0 : hello
thread 0 : wait for 100ms.
thread 3 : hello
thread 3 : wait for 100ms.
thread 0 : hello
thread 0 : wait for 100ms.
thread 3 : wait for 100ms.
thread 2 : wait for 100ms.
thread 1 : wait for 100ms.
...
六 注意
- std::condition_variable 只可与 std::unique_lock< std::mutex > 一同使用;此限制在一些平台上允许最大效率。
- std::condition_variable_any 提供可与任何基本可锁定 (BasicLockable) 对象,例如 std::shared_lock 一同使用的条件变量。std::condition_variable_any 能与 std::shared_lock 一同使用,以在 std::shared_mutex 上以共享所有权模式等待。
- 所以如果锁是 std::unique_lock,使用std::condition_variable以获得更好性能。