首页 > 编程语言 >【并发编程十】c++线程同步——条件变量(condition_variable)

【并发编程十】c++线程同步——条件变量(condition_variable)

时间:2023-03-16 16:35:00浏览次数:63  
标签:变量 lock c++ 互斥 线程 条件 variable include

 

【并发编程十】c++线程同步——条件变量(condition_variable)

 

简介:
本篇文章,我们详细的介绍下c++标准库提供的线程同步方法——条件变量(condition_variable)。

一、互斥

参见【并发编程九】c++线程同步——互斥(mutex)

二、条件变量

1、为何要引入条件变量?

  • 例子
    在一条生产线上有一个仓库,当生产者生产时需要锁住仓库独占,而消费者去产品时也需要锁住仓库独占。
    如果,生产者发现仓库满了,那么他就不能生产了,编程了阻塞状态。但是此时生产者独占仓库,消费者又无法进入仓库消耗产品,这样就造成了一个僵死的状态。

我们需要一种机制,当互斥量被锁住以后发现当前线程还是无法完成自己的操作,那么它应该释放互斥量,让其他线程哦工作。

  • 1、可以采用轮询的方式,不停的查询你需要的条件。
  • 2、让系统来帮你查询条件,使用条件变量。

2、不使用条件变量

  • demo
#include<iostream>
#include <thread>
#include<mutex>
#include<deque>
#include<chrono>

using namespace std;

mutex mtx;
deque<int> q;

//线程A,producer
void task1()
{
    int i = 0;
    while(true)
    {
        unique_lock<mutex> lock(mtx);
        if (q.size() < 1000)
        {
            if (i < 999)
            {
                q.push_back(i);
                i++;
            }
            else
            {
                i = 0;
            }
        }
        else
        {
            // std::this_thread::sleep_for(std::chrono::seconds(1));;
        }
    }
}

//线程B,consumer
void task2()
{
    int da = 0;
    while (true)
    {
        unique_lock<mutex> lock(mtx);
        if (!q.empty())
        {
            da = q.front();
            q.pop_front();
            cout << "get value from que:" << da << endl;
            cout << "que.size:" << q.size()<<endl;
        }
    }
}

int main()
{
    cout << "que.size:" << q.size() << endl;
    thread t2(task2);
    thread t1(task1);
 
    t1.join();
    t2.join();
}
  • 输出

3、使用条件变量

3.1、互斥锁有什么问题?

  • 尝试获取锁的人会一直等待,浪费cpu资源。(功耗和性能浪费)

3.2、条件变量

  • 提供睡眠/唤醒机制,避免无意义的等待。

条件变量是允许多个线程相互交流的同步原语。它允许一定量的线程等待(可以定时)另一线程的提醒,然后再继续。条件变量始终关联到一个互斥。
定义于头文件 <condition_variable>

3.3、条件变量成员函数

  • 通知
通知成员函数 解释
notify_one 通知一个等待的线程(公开成员函数)
notify_all 通知所有等待的线程(公开成员函数)
  • 等待
等待成员函数 解释
wait 阻塞当前线程,直到条件变量被唤醒(公开成员函数)
wait_for 阻塞当前线程,直到条件变量被唤醒,或到指定时限时长后(公开成员函数)
wait_until 阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点(公开成员函数)

简单说下,如果是新人,简单理解wait和notify_one两个函数就行了,基本就明白了条件变量的原理,如下面的demo,wait就是等待notify的通知后再执行

3.4、demo

#include<iostream>
#include <thread>
#include<mutex>
#include<deque>
#include<chrono>
#include<condition_variable>

using namespace std;

mutex mtx;
deque<int> q;
condition_variable cv;

//线程A,producer
void task1()
{
    int i = 0;
    while(true)
    {
        unique_lock<mutex> lock(mtx);
        if (q.size() < 1000)
        {
            if (i < 99)
            {
                q.push_back(i);
                cv.notify_one();//cv.notify_all();
                i++;
            }
            else
            {
                i = 0;
            }
        }
        else
        {
            cv.notify_one();
            //std::this_thread::sleep_for(std::chrono::seconds(1));;
        }
    }
}

//线程B,consumer
void task2()
{
    int da = 0;
    while (true)
    {
        unique_lock<mutex> lock(mtx);
        if (q.empty())//如果有多个消费者,此处应该为while(q.empty())
        {
            cv.wait(lock);
        }
        da = q.front();
        q.pop_front();
        cout << "get value from que:" << da << endl;
        cout << "que.size:" << q.size() << endl;
    }
}

int main()
{
    cout << "que.size:" << q.size() << endl;
    thread t2(task2);
    thread t1(task1);
 
    t1.join();
    t2.join();
}
  •  
  • 输出

  • cpu占用率

3.4、总结

  • 使用条件变量的意义在于,消费者在没有可消费的产品时,采用休眠,而非无意义的空转,浪费cpu的计算资源。

参考:
1、https://www.apiref.com/cpp-zh/cpp/thread.html
2、https://en.cppreference.com/w/cpp/thread
3、书籍《c++服务器开发精髓》——张远龙

三、future

四、信号量

 

标签:变量,lock,c++,互斥,线程,条件,variable,include
From: https://www.cnblogs.com/lidabo/p/17223100.html

相关文章

  • 【并发编程七】C++进程通信——套接字(socket)_80行代码实现一个聊天软件
     【并发编程七】进程通信——套接字(socket)_80行代码实现一个聊天软件一、简介二、相关知识介绍1、winsock1.h、winsock2.h2、如何使用ws2_32.dll3、WSAStartup......
  • 【并发编程三】C++进程通信——管道(pipe)
     【并发编程三】C++实现通信——管道(pipe)一、管道(pipe)二、匿名管道1、简介2、父子进程:匿名管道的通信过程?3、相关函数3.1、创建管道CreatePipe3.2、写入管......
  • 【并发编程六】c++进程通信——信号量(semaphore)
     【并发编程六】c++进程通信——信号量(semaphore)一、概述二、信号量三、原理四、过程1、进程A过程2、进程B过程五、demo1、进程A2、进程B六、输出......
  • redis线程模型-IO多路复用单线程通俗理解
     Redis单线程redis是以socket方式通信,socket服务端可同时接受多个客户端请求连接,也就是说,redis服务同时面对多个redis客户端连接请求,而redis服务本身是单线程运行。 ......
  • C++11中的:移动语义(std::move)、完美转发(std::forward)、std::ref和std::cref
    左值(lvalue)与右值(rvalue)左值与右值的概念其实在C++0x中就有了。概括的讲,凡是能够取地址的可以称之为左值,反之称之为右值,C++中并没有对左值和右值给出明确的定义,从其......
  • C/C++的const修饰符用法总结
    已经玩了C++三年的菜鸟一枚,因此本文部分内容可能有误,请见谅。作用:只读,不能修改。规则:const默认作用于其左边的东西,否则作用于其右边的东西。从右往左即可读懂。const......
  • C++ delete语句
    C++中的五种内存在C++中内存分为五个区:堆、栈、自由存储区、全局/静态存储区和常量存储区。堆区:用户使用new获得的内存在这里。用户需要自行管理其声明周期,也就是说一个......
  • C++ 异常
    C++异常exception《C++Primer中文版第五版》Ch5.61.try语句块和异常处理典型的异常失去数据库连接遇到意外输入如果程序的问题是输入无效,则异常处理部......
  • C++信号量实现线程间同步,windows使用SetEvent,linux使用sem_t,QT测试
     目录windows使用CreateEvent、SetEvent、ResetEvent、WaitForSingleObjectlinux使用sem_init、sem_wait、sem_trywait、sem_post、sem_destroy windows使用C......
  • c++ 创建对象
    1.定义类一般写在.h文件中#include<string>#include<iostream>usingnamespacestd;voidprint(charcontent[]);voidTestString1();voidTestString2();voidTe......