C++ 条件变量-生产消费者模型

条件变量是一种线程同步机制,当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒.

C++11的条件变量提供了两个类:

condition_variable:只支持与普通mutex搭配,效率更高。

condition_variable_any:是一种通用的条件变量,可以与任意mutex搭配(包括用户自定义的锁类型)

包含头文件:

1. condition_variable

主要成员函数:

1)condition_variable() 默认构造函数。

2)condition_variable(const condition_variable &)=delete 禁止拷贝。

3)condition_variable& condition_variable::operator=(const condition_variable &)=delete 禁止赋值。

4)notify_one() 通知一个等待的线程。

5)notify_all() 通知全部等待的线程。

6)wait(unique_lock lock) 阻塞当前线程,直到通知到达。

7)wait(unique_lock lock,Pred pred) 循环的阻塞当前线程,直到通知到达且谓词满足。

8)wait_for(unique_lock lock,时间长度)

9)wait_for(unique_lock lock,时间长度,Pred pred)

10)wait_until(unique_lock lock,时间点)

11)wait_until(unique_lock lock,时间点,Pred pred)

2. unique_lock

template class unique_lock是模板类,模板参数为互斥锁类型。

unique_locklock_guard都是管理锁的辅助类,都是RAII风格 在构造时获得锁,在析构时释放锁 。它们的区别在于:为了配合condition_variableunique_lock还有lock()unlock()成员函数。

代码示例1:

#include 

#include 

#include                       // 线程类头文件。

#include                       // 互斥锁类的头文件。

#include                       // deque容器的头文件。

#include                       // queue容器的头文件。

#include   // 条件变量的头文件。

using namespace std;

class AA

{

    mutex m_mutex;                                    // 互斥锁。

    condition_variable m_cond;                  // 条件变量。

    queue> m_q;   // 缓存队列,底层容器用deque。

public:

    void incache(int num)     // 生产数据,num指定数据的个数。

    {

        lock_guard lock(m_mutex);   // 申请加锁。

        for (int ii=0 ; ii,并申请加锁。

                unique_lock lock(m_mutex);



                while (m_q.empty())    // 如果队列空,进入循环,否则直接处理数据。必须用循环,不能用if

                    m_cond.wait(lock);  // 等待生产者的唤醒信号。



                // 数据元素出队。

                message = m_q.front();  m_q.pop();
                cout << "线程:" << this_thread::get_id() << "," << message << endl;
     

            }//添加{}自定义作用域提前解锁,数据出队后进行处理的过程无需上锁,提高效率.

            // 处理出队的数据(把数据消费掉)。

            this_thread::sleep_for(chrono::milliseconds(1));   // 假设处理数据需要1毫秒。

           
        }

    }

};



int main()

{

    AA aa;

  

    thread t1(&AA::outcache, &aa);     // 创建消费者线程t1。

    thread t2(&AA::outcache, &aa);     // 创建消费者线程t2。

    thread t3(&AA::outcache, &aa);     // 创建消费者线程t3。



    this_thread::sleep_for(chrono::seconds(2));    // 休眠2秒。

    aa.incache(3);      // 生产3个数据。



    this_thread::sleep_for(chrono::seconds(3));    // 休眠3秒。

    aa.incache(5);      // 生产5个数据。



    t1.join();   // 回收子线程的资源。

    t2.join();

    t3.join();

}

代码示例2:

#include 

#include 

#include                       // 线程类头文件。

#include                       // 互斥锁类的头文件。

#include                       // deque容器的头文件。

#include                       // queue容器的头文件。

#include   // 条件变量的头文件。

using namespace std;

class AA

{

    mutex m_mutex;                                    // 互斥锁。

    condition_variable m_cond;                  // 条件变量。

    queue> m_q;   // 缓存队列,底层容器用deque。

public:

    void incache(int num)     // 生产数据,num指定数据的个数。

    {

        lock_guard lock(m_mutex);   // 申请加锁。

        for (int ii=0 ; ii,并申请加锁。

            unique_lock lock(m_mutex);



            // 条件变量虚假唤醒:消费者线程被唤醒后,缓存队列中没有数据。

            //while (m_q.empty())    // 如果队列空,进入循环,否则直接处理数据。必须用循环,不能用if

            //    m_cond.wait(lock);  // 1)把互斥锁解开;2)阻塞,等待被唤醒;3)给互斥锁加锁。

            m_cond.wait(lock, [this] { return !m_q.empty(); });



            // 数据元素出队。

            string message = m_q.front();  m_q.pop();

            cout << "线程:" << this_thread::get_id() << "," << message << endl;

            lock.unlock();      // 手工解锁。



            // 处理出队的数据(把数据消费掉)。

            this_thread::sleep_for(chrono::milliseconds(1));   // 假设处理数据需要1毫秒。

        }

    }

};



int main()

{

    AA aa;

  

    thread t1(&AA::outcache, &aa);     // 创建消费者线程t1。

    thread t2(&AA::outcache, &aa);     // 创建消费者线程t2。

    thread t3(&AA::outcache, &aa);     // 创建消费者线程t3。



    this_thread::sleep_for(chrono::seconds(2));    // 休眠2秒。

    aa.incache(2);      // 生产2个数据。



    this_thread::sleep_for(chrono::seconds(3));    // 休眠3秒。

    aa.incache(5);      // 生产5个数据。



    t1.join();   // 回收子线程的资源。

    t2.join();

    t3.join();

} 

注意:

   1.notify_once()只唤醒一个被当前条件变量(锁)阻塞的线程,不是随机的,是按线程阻塞队列的顺序唤醒(先进先出). notify_all()唤醒全部被当前条件变量(锁)阻塞的线程,唤醒顺序也是按线程阻塞队列的顺序唤醒. notify_all()的处理效率更高,具体使用哪个看应用环境.

   2. wait(lock)函数有三个步骤:解锁,阻塞等待.重新加锁.如果不用循环而是用if,则notify_all()可能会出现虚假唤醒的情况,即队列为空但消费者线程仍运行.

   3.wait(lock,谓词)重载函数第二个参数用lambda函数更便捷,该重载函数内置循环判断.

   4.  C++队列queue模板类的定义在头文件中,queue 模板类需要两个模板参数,一个是元素类型,一个容器类型,元素类型是必要的,容器类型是可选的,默认为deque 类型.

注:此课件及源代码来自B站up主:码农论坛,该文章仅作为本人学习笔记及交流学习使用,本人仅做整理并补充学习理解和知识点。 

你可能感兴趣的:(c++,开发语言,学习,笔记,多线程)