1. 动机:实现高效安全处理线程中的阻塞,等待
为了处理多线程中的阻塞,等待,特别是条件等待的问题。引入 condional_variable。可以用在主线程中,也可以用在子线程中。用于等待,其他转移状态满足。 类似代替了 while()循环实现的等待,类似 C# 中await ( )。
2. 代码实现
《现代 C++ 教程:高速上手 C++ 11/14/17/20》书中,
#include <queue>
#include <chrono>
#include <mutex>
#include <thread>
#include <iostream>
#include <condition_variable>
int main() {
std::queue<int> produced_nums;
std::mutex mtx;
std::condition_variable cv;
bool notified = false; // 通知信号
// 生产者
auto producer = [&]() {
for (int i = 0; ; i++) {
std::this_thread::sleep_for(std::chrono::milliseconds(900));
std::unique_lock<std::mutex> lock(mtx);
std::cout << "producing " << i << std::endl;
produced_nums.push(i);
notified = true;
cv.notify_all(); // 此处也可以使用 notify_one
}
};
// 消费者
auto consumer = [&]() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
while (!notified) { // 避免虚假唤醒
cv.wait(lock);
}
// 短暂取消锁,使得生产者有机会在消费者消费空前继续生产
lock.unlock();
// 消费者慢于生产者
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
lock.lock();
while (!produced_nums.empty()) {
std::cout << "consuming " << produced_nums.front() << std::endl;
produced_nums.pop();
}
notified = false;
}
};
// 分别在不同的线程中运行
std::thread p(producer);
std::thread cs[2];
for (int i = 0; i < 2; ++i) {
cs[i] = std::thread(consumer);
}
p.join();
for (int i = 0; i < 2; ++i) {
cs[i].join();
}
return 0;
}
3. 其他问题——虚假唤醒问题(Spurious wakeups)
虚假唤醒问题 avoiding spurious wakeups
对比条件,wai() 休眠的地方,一般有两种方式。
- 条件判断可以用 while 管理起来。如,这里 !ready
- 可以放到 wait的重载中, 如这里,[]{ return ready; }
std::unique_lock<std::mutex> lock(mutex);
while (!ready) {
condition.wait(lock);
}
std::unique_lock<std::mutex> lock(mutex);
condition.wait(lock, []{ return ready; });
我认为,第二种应该更好。第一种还是回发生,虚假唤醒,代码如下。二第二种,使用了condition.wait
的另一个重载版本,该版本接受一个lambda表达式作为参数,用于检查条件是否为true
。这样一来,线程会一直处于睡眠状态,直到lambda表达式返回true
。
安全起见,唤醒后,还是要检查,数据的有效性。
//还是有 supious wakeup的问题
std::mutex mutex;
std::condition_variable condition;
bool ready = false;
void thread_func() {
std::unique_lock<std::mutex> lock(mutex);
while (!ready) {
condition.wait(lock);
}
std::cout << "Thread woke up, ready = " << ready << std::endl;
}
int main() {
std::thread t(thread_func);
{
std::lock_guard<std::mutex> lock(mutex);
// 假设在这里发生虚假唤醒
}
// 假设在这里调用 notify_one
condition.notify_one();
t.join();
return 0;
}