c++线程同步——条件变量(condition_variable)

简介: c++线程同步——条件变量(condition_variable)

@[TOC]

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();
}

3.4、总结

  • 使用条件变量的意义在于,消费者在没有可消费的产品时,采用休眠,而非无意义的空转,浪费cpu的计算资源。
相关文章
|
16天前
|
Java 开发者
解锁并发编程新姿势!深度揭秘AQS独占锁&ReentrantLock重入锁奥秘,Condition条件变量让你玩转线程协作,秒变并发大神!
【8月更文挑战第4天】AQS是Java并发编程的核心框架,为锁和同步器提供基础结构。ReentrantLock基于AQS实现可重入互斥锁,比`synchronized`更灵活,支持可中断锁获取及超时控制。通过维护计数器实现锁的重入性。Condition接口允许ReentrantLock创建多个条件变量,支持细粒度线程协作,超越了传统`wait`/`notify`机制,助力开发者构建高效可靠的并发应用。
36 0
|
5天前
|
Java 调度
基于C++11的线程池
基于C++11的线程池
|
21天前
|
存储 SQL Java
(七)全面剖析Java并发编程之线程变量副本ThreadLocal原理分析
在之前的文章:彻底理解Java并发编程之Synchronized关键字实现原理剖析中我们曾初次谈到线程安全问题引发的"三要素":多线程、共享资源/临界资源、非原子性操作,简而言之:在同一时刻,多条线程同时对临界资源进行非原子性操作则有可能产生线程安全问题。
|
26天前
|
存储 Python 容器
Node中的AsyncLocalStorage 使用问题之在Python中,线程内变量的问题如何解决
Node中的AsyncLocalStorage 使用问题之在Python中,线程内变量的问题如何解决
|
26天前
|
算法 编译器 C++
开发与运维线程问题之在C++的原子操作中memory_order如何解决
开发与运维线程问题之在C++的原子操作中memory_order如何解决
21 2
|
4天前
|
Dart 编译器 API
Dart ffi 使用问题之在C++线程中无法直接调用Dart函数的问题如何解决
Dart ffi 使用问题之在C++线程中无法直接调用Dart函数的问题如何解决
|
4天前
|
Dart API C语言
Dart ffi 使用问题之想在C/C++中创建异步线程来调用Dart方法,如何操作
Dart ffi 使用问题之想在C/C++中创建异步线程来调用Dart方法,如何操作
|
23天前
|
存储 缓存 算法
同时使用线程本地变量以及对象缓存的问题
【7月更文挑战第15天】同时使用线程本地变量和对象缓存需小心处理以避免数据不一致、竞争条件及内存泄漏等问题。线程本地变量使各线程拥有独立存储,但若与对象缓存关联,可能导致多线程环境下访问旧数据。缺乏同步机制时,多线程并发修改缓存中的共享对象还会引起数据混乱。此外,若线程结束时未释放对象引用,可能导致内存泄漏。例如,在Web服务器场景下,若一更新缓存而另一线程仍获取旧数据,则可能返回错误信息;在图像处理应用中,若多线程无序修改算法对象则可能产生错误处理结果。因此,需确保数据一致性、避免竞争条件并妥善管理内存。
|
26天前
|
Rust 安全 程序员
Rust与C++的区别及使用问题之Rust解决多线程下的共享的问题如何解决
Rust与C++的区别及使用问题之Rust解决多线程下的共享的问题如何解决
|
26天前
|
Rust 编译器 程序员
Rust与C++的区别及使用问题之Rust避免多线程中的lifetime的问题如何解决
Rust与C++的区别及使用问题之Rust避免多线程中的lifetime的问题如何解决