• 14 C++11线程同步之条件变量


    在学习条件变量之前需要先了解下std::unique_lock;条件变量 condition_variable需要配合std::unique_lock使用;

    std::unique_lock

    std::unique_lock的详细细节参考此篇文章

    C11条件变量

    条件变量是 C++11 提供的另外一种用于等待的同步机制,它能阻塞一个或多个线程,直到收到另外一个线程发出的通知或者超时时,才会唤醒当前阻塞的线程。条件变量需要和互斥量配合起来使用,C++11 提供了两种条件变量:

    • condition_variable:需要配合std::unique_lockstd::mutex进行wait操作,也就是阻塞线程操作;
    • condition_variable_any:可以和任意带有lock()、unlock()语意的mutex搭配使用:
      • std::mutex:独占的非递归互斥锁
      • std::timed_mutex:带超时的独占非递归互斥锁
      • std::recursive_mutex:不带超时功能的递归互斥锁
      • std::recursive_timed_mutex:带超时的递归互斥锁

    1. condition_variable

    1.1 成员函数

    condition_variable的成员函数主要分为两部分:线程阻塞函数和线程通知函数,这些函数定于头文件

    // ①
    void wait (unique_lock<mutex>& lck);
    // ②
    template <class Predicate>
    void wait (unique_lock<mutex>& lck, Predicate pred);
    //③
    template <class Rep, class Period>
    cv_status wait_for (unique_lock<mutex>& lck,
                        const chrono::duration<Rep,Period>& rel_time);
    //④
    template <class Rep, class Period, class Predicate>
    bool wait_for(unique_lock<mutex>& lck,
                   const chrono::duration<Rep,Period>& rel_time, Predicate pred);
    //⑤
    template <class Clock, class Duration>
    cv_status wait_until (unique_lock<mutex>& lck,
                          const chrono::time_point<Clock,Duration>& abs_time);
    //⑥
    template <class Clock, class Duration, class Predicate>
    bool wait_until (unique_lock<mutex>& lck,
                     const chrono::time_point<Clock,Duration>& abs_time, Predicate pred);
    //⑦
    void notify_one() noexcept;
    //⑧
    void notify_all() noexcept;                 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    函数①:调用该函数的线程直接被阻塞

    函数②:该函数的第二个参数是一个判断条件,是一个返回值为布尔类型的函数

    该参数可以传递一个有名函数的地址,也可以直接指定一个匿名函数
    表达式返回false当前线程被阻塞,表达式返回true当前线程不会被阻塞,继续向下执行

    函数③、④:
    wait_for() 函数和 wait() 的功能是一样的,只不过多了一个阻塞时长,假设阻塞的线程没有被其他线程唤醒,当阻塞时长用完之后,线程就会自动解除阻塞,继续向下执行。
    函数⑤、⑥:
    wait_until() 函数和 wait_for() 的功能是一样的,它是指定让线程阻塞到某一个时间点,假设阻塞的线程没有被其他线程唤醒,当到达指定的时间点之后,线程就会自动解除阻塞,继续向下执行。

    如果线程被该函数阻塞,这个线程会释放占有的互斥锁的所有权,当阻塞解除之后这个线程会重新得到互斥锁的所有权,继续向下执行(这个过程是在函数内部完成的,了解这个过程即可,其目的是为了避免线程的死锁)

    函数⑦、⑧
    notify_one():唤醒一个被当前条件变量阻塞的线程
    notify_all():唤醒全部被当前条件变量阻塞的线程

    eg:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    class SyncQueue
    {
    public:
        SyncQueue(int maxSize) :m_maxSize(maxSize) {}
        void put(const int& x)
        {
            unique_lock<mutex> locker(m_mutex);
            
            //while (m_queue.size() == m_maxSize)
            //{
            //    cout << "任务队列已满,请耐心等待..." << endl;
            //    m_notFull.wait(locker);
            //}
    
            m_notFull.wait(locker, [this]() { return m_maxSize != m_queue.size();});
            m_queue.push_back(x);
            cout << x << "被生产" << endl;
            m_notEmpty.notify_one();
        }
    
        int take()
        {
            unique_lock<mutex> locker(m_mutex);
    
            //while (m_queue.empty())
            //{
            //    cout << "任务队列已空,请耐心等待..." << endl;
            //    m_notEmpty.wait(locker);
            //}
    
            m_notEmpty.wait(locker, [this]() { return m_maxSize != m_queue.size(); });
            int x = m_queue.front();
            m_queue.pop_front();
            m_notFull.notify_one();
            cout << x << "被消费" << endl;
            return x;
        }
    
    private:
        list<int> m_queue;
        mutex m_mutex;
        condition_variable m_notEmpty;
        condition_variable m_notFull;
        int m_maxSize;
    
    };
    
    int main()
    {
        SyncQueue taskQ(50);
        auto produce = bind(&SyncQueue::put, &taskQ, placeholders::_1);
        auto consume = bind(&SyncQueue::take, &taskQ);
        thread t1[3];
        thread t2[3];
        for (int i = 0; i < 3; i++)
        {
            t1[i] = thread(produce, i + 100);
            t2[i] = thread(consume);
        }
    
        for (int i = 0; i < 3; ++i)
        {
            t1[i].join();
            t2[i].join();
        }
        system("pause");
        return 0;
    }   
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    在这里插入图片描述

    2. condition_variable_any

    condition_variable_any 用法与condition_variable类似,区别如下:
    condition_variable 配合 unique_lock 使用更灵活一些,可以在在任何时候自由地释放互斥锁,而 condition_variable_any 如果和 lock_guard 一起使用必须要等到其生命周期结束才能将互斥锁释放。但是,condition_variable_any 可以和多种互斥锁配合使用,应用场景也更广,而 condition_variable 只能和独占的非递归互斥锁(mutex)配合使用,有一定的局限性。

    具体用法参考该文章

    3. 案例

    看下LeetCode上一个题,用条件变量来解:
    给你一个类:
    在这里插入图片描述
    在这里插入图片描述

  • 相关阅读:
    linux上安装nginx
    git-Reset 三种模式
    学习编程语言需要熟悉库函数吗?
    利用excel表格进行分包和组包
    贪心算法找零问题
    生成式AI - 大模型(LLM)提示工程(Prompt)技巧
    ChatGPT详细搭建教程+支持AI绘画
    SpringBoot中Bean的创建过程及扩展操作点 @by_TWJ
    02_数据类型及其运算
    (02)Cartographer源码无死角解析-(28) GlobalTrajectoryBuilder构建过程与整体分析
  • 原文地址:https://blog.csdn.net/Snow__Sunny/article/details/127850177