admin管理员组文章数量:1584840
理解 C++ 中的条件变量(Condition Variable)
在多线程编程中,我们常常需要一个线程等待某个条件的变化,比如等待数据的生成或某个标志位的设置。如果没有条件变量(condition_variable
),线程可能会使用忙等待(不断检查条件是否满足),这会导致 CPU 资源的浪费。条件变量提供了一种高效的等待机制,使线程在等待条件时进入休眠状态,不占用 CPU 资源,当条件满足时被唤醒继续执行。
条件变量的基本概念
条件变量允许一个或多个线程同时阻塞。一般情况下,生产者线程利用支持 std::mutex
的 std::lock_guard
或 std::unique_lock
修改共享变量后,并通知条件变量。消费者线程获取同一个 std::mutex
(由 std::unique_lock
持有),并调用 std::condition_variable
的 wait
、wait_for
或 wait_until
。wait
操作会释放互斥量,同时挂起该线程。当条件变量收到通知、超时到期或发生虚假唤醒时,线程被唤醒,互斥量也会被原子地重新获取。如果是虚假唤醒,线程应该检查条件并继续等待,以保证业务的正确性。
注意事项
-
使用
unique_lock
而不是lock_guard
:
在等待时,管理mutex
使用的是unique_lock
而不是lock_guard
,因为等待时是不持有锁的。wait
函数会调用mutex
的unlock
函数,之后再睡眠,直到被唤醒后才持有锁。lock_guard
没有lock/unlock
接口,所以需要用unique_lock
。 -
伪唤醒:
在对wait
函数的调用中,条件变量可能会对提供的条件检查任意多次。这发生在互斥元被锁定的情况下,并且当测试条件返回 true 时就会立即返回。当等待线程重新获取互斥元并检测条件时,如果它并非直接响应另一个线程的通知,这就是所谓的伪唤醒(spurious wake)。伪唤醒的次数和频率根据定义是不确定的。 -
通知丢失:
如果发送方在接收方进入等待状态之前发送通知,则通知会丢失。
使用场景
-
生产者-消费者问题:
生产者线程生成数据并通知消费者线程处理数据。 -
任务队列:
多个线程从一个任务队列中获取任务并处理,当队列为空时,线程进入等待状态,直到有新的任务被添加。 -
事件等待:
一个或多个线程等待某个事件的发生,当事件发生时,唤醒等待的线程进行处理。
std::condition_variable
和 std::condition_variable_any
的区别
-
互斥锁类型:
std::condition_variable
只与std::unique_lock<std::mutex>
类型的锁配合使用。这意味着它只能与std::mutex
类型的互斥锁一起使用。std::condition_variable_any
可以与任何符合基本锁(BasicLockable)和锁互换(Lockable)概念的锁对象配合使用。它不仅可以与std::mutex
配合,还可以与其他类型的锁(例如std::shared_mutex
、用户定义的互斥锁等)一起使用。
-
灵活性:
std::condition_variable
更加专用,提供了一种高效的实现,因为它只支持std::unique_lock<std::mutex>
。std::condition_variable_any
更加通用,提供了更大的灵活性,可以与任何符合要求的锁一起使用,因此在某些情况下更为便利。
为什么要有 std::condition_variable_any
?
std::condition_variable_any
的引入是为了提供更大的灵活性,允许开发者使用不同类型的锁来实现同步需求。在某些情况下,开发者可能需要使用自定义的锁类型或者 std::shared_mutex
这样的共享锁,这时 std::condition_variable
就无法满足需求,而 std::condition_variable_any
则可以适应这些情况。
代码示例
以下是一个使用条件变量的代码示例,演示了如何在 C++ 中使用 std::condition_variable
进行线程同步:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
std::mutex read_mutex_;
std::condition_variable condition_variable_;
bool readFlag_ = false;
void do_print_id(int id) {
std::unique_lock<std::mutex> uniqueLock(read_mutex_);
/**
* todo -
* 只有当 __pred 条件为 false 时调用 wait() 才会阻塞当前线程,并且在收到其他线程的通知后只有当 __pred 为 true 时才会被解除阻塞
* 1. 线程第一次执行到这里时,会执行表达式方法。判断到 ready 为false。则当前线程阻塞在此,同时解锁互斥量,不影响其他线程获取锁
* 2. 当线程被唤醒,首先就是不断的尝试重新获取并加锁互斥量,若获取不到锁就卡在这里反复尝试加锁
* 3. 若获取到了锁,就执行表达式方法,然后继续往下执行
*
* 函数原型:void condition_variable::wait(unique_lock<mutex>& __lk, _Predicate __pred)
*/
condition_variable_.wait(uniqueLock, [&] {
std::cout << "condition_variable wait.id: " << id << std::endl;
return readFlag_;
});
std::cout << "do_print_id -> thread : " << id << std::endl;
}
class QConditionVariable {
public:
void task1() {
std::thread threads[10];
for (int i = 0; i < 10; ++i) {
threads[i] = std::thread(do_print_id, i);
}
for (std::thread &item : threads) {
item.detach();
}
std::this_thread::sleep_for(std::chrono::seconds(2));
notify();
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "task1--end" << std::endl;
}
void task2() {
std::unique_lock<std::mutex> uniqueLock(read_mutex_);
std::cv_status cvStatus = condition_variable_.wait_for(uniqueLock, std::chrono::seconds(5));
if (cvStatus == std::cv_status::no_timeout) {
//表示条件变量等待成功(条件满足或被通知)。
} else if (cvStatus == std::cv_status::timeout) {
//表示条件变量等待超时。
}
}
void task3() {
std::unique_lock<std::mutex> uniqueLock(read_mutex_);
/**
* wait_for: 等待特定的时间段,直到被通知或时间到期。
* 执行到这里时,当前线程将进入等待状态,等待最多1秒钟:
* 如果在这1秒钟内,条件变量 condition_variable_ 被其他线程通知(通常通过 notify_one 或 notify_all),wait_for 会立即返回 std::cv_status::no_timeout,并且 while 循环会终止。
* 如果这1秒钟内没有收到通知,wait_for 返回 std::cv_status::timeout,循环条件为真,线程继续执行循环体内的代码(这里是空的,没有其他操作),然后再次进入等待。
*/
while (condition_variable_.wait_for(uniqueLock, std::chrono::seconds(1)) == std::cv_status::timeout) {
}
}
private:
void notify() {
std::cout << "start----notify" << std::endl;
std::unique_lock<std::mutex> uniqueLockNotify(read_mutex_);
readFlag_ = true;
condition_variable_.notify_all();
std::cout << "end----notify" << std::endl;
}
};
在这个示例中,我们创建了一个简单的类 QConditionVariable
,包含了两个任务 task1
和 task2
。task1
生成 10 个线程,每个线程都会调用 do_print_id
函数,等待条件变量 condition_variable_
的通知。task2
则是一个等待操作示例,等待特定时间段或直到被通知。
通过条件变量,我们可以有效地管理多线程程序中的同步和通信,避免忙等待,提高程序的执行效率。
本文标签: 变量条件conditionvariable
版权声明:本文标题:C++中的condition_variable:条件变量 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dianzi/1727949032a1139251.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论