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

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

posted @   Emma1111  阅读(2268)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示