首页 > 编程语言 >【condition_variable】C++条件变量的原理和用法

【condition_variable】C++条件变量的原理和用法

时间:2023-01-08 22:55:38浏览次数:53  
标签:std pred C++ 线程 variable condition wait

condition_variable简介

condition_variable类是一个同步原语,与std::mutex一起使用,用于阻塞一个或多个线程,直到另一个线程修改一个共享变量(条件)并通知condition_variable。condition_variable主要有wait函数和notify_* 函数,wait的作用是等待,notify的作用是通知。Linux环境下的相关接口pthread_cond_*等函数。

当 std::condition_variable对象的某个wait 函数被调用的时候,它使用 std::unique_lock(通过 std::mutex) 来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的 std::condition_variable 对象上调用了 notification 函数来唤醒当前线程。

wait()还有第二个参数,这个参数接收一个布尔类型的值,当这个布尔类型的值为false的时候线程就会被阻塞在这里,只有当线程被唤醒之后,且第二参数为true才会往下运行。

notify_one()每次只能唤醒一个线程,notify_all()函数的作用就是可以唤醒所有的线程,但是最终能抢夺锁的只有一个线程,或者说有多个线程在wait,但是用notify_one()去唤醒其中一个线程,那么这些线程就出现了去争夺互斥量的情况,最终没有获得锁的控制权的线程就会再次回到阻塞的状态,对于这些没有抢到控制权的过程叫做虚假唤醒。对于虚假唤醒的解决方法就是加一个while()循环,比如:

while(que.size() == 0)
{
    cr.wait(lck);
}

或者

if(que.empty())
{
    cr.wait(clk);
}

这个就是当线程被唤醒以后,先进行判断,是否可以去操作,如果可以,再去运行下面的代码,否则继续在循环内执行wait函数

上面所说多个线程等待一个唤醒的情况叫做惊群效应。

condition_variable类

std::condition_variable::wait():

方法原型:

void wait (unique_lock<mutex>& lck, Predicate pred);

std::condition_variable提供了两种wait()函数。当前线程调用wait()后被阻塞(此时当前线程获得了mutex),直到另外某个线程调用notify_*唤醒了当前线程。

在当前线程被阻塞时,该函数会自动调用lck.unlock()释放锁,使得其他被阻塞在锁竞争上的线程得以继续执行。一旦当前线程获得通知(通常是另外某个线程调用notify_* 唤醒了当前线程),wait()函数也是自动调用lck.lock(),使得lck的状态和wait函数被调用时相同。

在设置了pred的情况下,只有当pred条件为false时调用wait()才会阻塞当前线程,并且在收到其他线程的通知后只有当pred为true时才会被解除阻塞。等效于以下情景。

while(!pred())
{
    wait(lck);
}

std::condition_variable::wait_for():

方法原型:

cv_status wait_for (unique_lock<mutex>& lck,
                      const chrono::duration<rep,period>& rel_time);

bool wait_for (unique_lock<mutex>& lck,
                      const chrono::duration<rep,period>& rel_time, Predicate pred);

与std::condition_variable::wait()类似,不过wait_for可以指定一个时间段,在当前线程收到通知或者指定的时间rel_time超时之前,该线程都会处于阻塞状态。一旦超时或收到了其他线程的通知,wait_for返回,剩下的处理步骤和wait()类似。

wait_for()的pred表示预测条件,只有当pred条件为false时调用wait()才会阻塞当前线程,并且在收到其他线程的通知后只有当pred为true时才会被解除阻塞。

std::condition_variable::wait_until():

方法原型:

cv_status wait_until (unique_lock<mutex>& lck,
                        const chrono::time_point<clock,duration>& abs_time);

bool wait_until (unique_lock<mutex>& lck,
                        const chrono::time_point<clock,duration>& abs_time,
                        Predicate pred);

与wait_for类似,但是wait_until可以指定一个时间点,在当前线程收到通知或者指定的时间点abs_time超时之前,该线程都处于阻塞状态。一旦超时或者收到了其他线程的通知,wait_until返回,剩下的处理步骤和wait()相似

wait_until的重载版本中pred表示wait_until的预测条件,只有当pred条件为false时调用wait()才会阻塞当前线程,并且在收到其他线程的通知后只有当pred为true时才会被解除阻塞

std::condition_variable::notify_one():

唤醒某个等待(wait)线程。如果当前没有等待线程,则该函数什么也不做,如果同时存在多个等待线程,则唤醒某个线程是不确定的。

std::condition_variable::notify_all():

唤醒所有等待(wait)线程。如果当前没有等待线程,则什么也不做。

辅助函数std::notify_all_at_thread_exit():

通知其他线程,给定的线程已经全部完成,当调用该函数的线程退出时,所有在cond条件变量上等待的线程都会收到通知

std::condition_variable_any类

与std::condition_variable类似,只不过std::condition_variable_any的wait函数可以接受任何lockadle参数。而std::condition_variable只能接受std::unique_lock类型的参数,除此意外和std::condition_variable几乎完全一样。

condition_variable代码实例

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
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);
    // wait第二个参数返回值为bool类型,如果返回为false当前线程则被锁住
    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();
}

运行结果:

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










参考文章:

https://cloud.tencent.com/developer/article/1584067
https://en.cppreference.com/w/cpp/thread/condition_variable
https://blog.csdn.net/lv0918_qian/article/details/81745723

标签:std,pred,C++,线程,variable,condition,wait
From: https://www.cnblogs.com/Wangzx000/p/17035639.html

相关文章

  • C++教学创新大赛信息管理系统[2023-01-08]
    C++教学创新大赛信息管理系统[2023-01-08]2022级《计算思维综合实践I》课程任务书及相关要求适用班级:计算机类2022级、大数据2022级、人工智能2022级一、课程目标1.【......
  • C++指针【cherno课程学习】
    定义:指针是一个整数,一种存储内存地址的数字内存就像一条线性的线,在这条街上的每一个房子都有一个号码和地址类似比喻成电脑,这条街上每一个房子的地址是一个字节我们......
  • C++实现双向链表的相关操作代码
    #include<iostream>#include<cstdlib>usingnamespacestd;#defineOK1#defineERROR0#defineMAXSIZE100typedefintElemtype;typedefintStatus;typedefstructDuL......
  • c++ vector容器总结
    vector1.动态扩展:并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝新空间,释放原空间。(一般会找比预料更多的空间)2.vector容器构造1.构造vector​​<int>......
  • c++使用接口,通过纯虚函数实现
    #include<iostream>usingnamespacestd;classShape{protected:intwidth;intheight;stringname;public://purevirtialfunctionfori......
  • C++核心知识回顾(函数&参数、异常、动态分配)
    复习C++的核心知识函数与参数传值参数、模板函数、引用参数、常量引用参数传值参数intabc(inta,intb,intc){returna+b*c;}a、b、c是函数abc的形参,下......
  • c++ virtual关键字学习
    virtual在类中使用如在多继承中(环状继承):classD{......};classB:publicD{......};classA:publicD{......};classC:publicB,publicA{.....};这个继承......
  • C++ - 多线程
    1.多线程传统的C++(C++11之前)中并没有引入线程这个概念,在C++11出来之前,如果我们想要在C++中实现多线程,需要借助操作系统平台提供的API,比如Linux的<pthread.h>,或者windows下......
  • [C++/Java/Py/C#/Ruby/Swift/Go/Scala/Kotlin/Rust/PHP/TS/Elixir/Dart/Racket/Erlang
    目录题解地址代码cppjavapython3C#rubyswiftgolangscalakotlinrustphptypescriptelixirdartracketerlang题解地址https://leetcode.cn/problems/counting-words-with-a-g......
  • C++初探索
    C++初探索前言C++和C的区别主要在8个方面:输入和输出引用inline函数函数默认值函数重载模板函数new和deletenamespace我仅对印象不深的地方做了总结。......