zoukankan      html  css  js  c++  java
  • 关于c++中条件变量的分析

    Hello,各位看官好,本人最近研究了关于各种锁以及条件变量的用法,到此来跟各位分享一下。

     

    一、线程中间存在的同步和互斥的问题

    二、lock_guardunique_lock的区别

    三、关于c++中所有锁的用法和区别

    四、Condition_variable的用法

    五、实例分析

     

    一、线程中间存在的同步和互斥的问题

    在操作系统中,多线程一直是一大问题,最大的问题可能就是并发访问时候出现的创建,读取,涂写,删除等操作,导致不可预测的结果。我们这里简单来分析一下。

    首先我们来记住一句话,只有在一种情况下我们不需要担心同步和互斥的问题:

    多个线程并发处理相同的数据而又不曾同步化,那么唯一安全的情况就是:所有线程只读取数据。

    如果不是这种情况会怎么样呢?会导致data race,即是不同线程中的两个互相冲突的动作,其中至少一个不是atomic的,而且无一个动作发生在另一个动作之前。这会导致不可预知的错误。

    那么,会导致什么结果呢?我们来看以下可能:

    1、写入半途的数据(如果再写入)

    2、传递参数时有可能会导致重排顺序

    那么针对这个问题我们应该如何处理呢?处理的关键点有两个:1、第一就是我们必须保证其是不可分割的。2、我们必须保证其是有顺序的。也就是我们今天讨论的同步和互斥。

    二、lock_guardunique_lock的区别

    我们首先来说互斥,互斥最常用的方法就是lock_guardunique_lock这两个锁,那么这两个锁究竟有什么区别呢?我们首先来看lock_guard

    while(true) {

    std::lock_guard<std::mutex> lock(my_lock);

    if (num < 100)

    {

    //运行条件 num += 1; sum += num;

    }

    else {

    //退出条件 break;

    }

    }

    我们来看这段代码,最奇怪的地方在于他没有上锁和解锁的过程。首先我们要知道,lock_guard是一个类,这个类中一定是有构造和析构函数的,那么lock_guard正式利用了这一点,在构造的时候加锁,在析构的时候解锁。那么构造好说,但是什么时候析构呢?就是在碰到我们第二个括号的时候。

    另外就是这段代码还有一点我们要注意,就是在判断条件的时候一定要用while,而不能用if,为什么呢?这是因为在锁中会存在一个虚假唤醒的问题,如果用if,就不会对其进行二次检查,而如果用while就可以完美解决这个问题了。

    接下来我们说一说unique_lock这个锁是lock_guard的升级版本,那么它究竟升级到哪里了呢?就是它可以自由的判断出锁是否添加上,以及是否解除锁。另外,他可以自由的决定是不是一开始就加锁。举例如下。

    If (lock()) {  // 判断是否加锁成功

    }

     

    If (try_lock()) {  // 判断是否加锁,如果没有加锁

    }

     

    Std::unique_lock<std::mutex> lockM1(m1, std::defer_lock)  //初始化的时候不加锁

     

    三、关于c++中各种锁的使用

    我们知道,在c++中有各种锁,比如互斥锁,自旋锁,读写锁这三种比较常用,我们刚才介绍了互斥锁,我们简单说下自旋锁,自旋锁和互斥锁的区别在于自旋锁是阻塞锁,互斥锁是非阻塞锁,就是说如果两个线程,线程一用了互斥锁,线程二碰到互斥锁此时会处理别的事情,而碰到自旋锁会导致一直阻塞到那里。而读写锁是针对读写操作的一种特殊锁,它的核心就是一写多读。(只是目前我只用到了互斥锁)

    四、条件变量

    条件变量更多的用于同步。它的核心就是condition_variable。这里有以下几点需要说明:

    1、条件变量一定要和锁一块用,同步的前提是互斥。

    2、这里最核心的两个函数,一个是wait函数,另一个是notify函数。

    Wait函数:原型为wait(mutex, function) (前者是锁,后者是判断的函数(可以用Lamaba表达式来进行书写))。另外,最重要的一点(如果wait里面的条件不满足,他会阻塞,会挂起。如果满足正常执行)。

    Notify函数,在所有东西弄完了以后,使用notify函数通知其他的可以用了。

    五、实例分析

    #include "mainwindow.h"

    #include <QApplication>

    #include <thread>

    #include <mutex>

    #include <condition_variable>

    #include <deque>

    #include <iostream>

     

    #define MAX_THREAD 3

    #define MAX_NUM 30

    std::mutex g_pcMutex;

    std::condition_variable g_pcCondition;

    std::deque<int> g_package;

    int g_nextIndex = 0;

     

    void productorFunction(int id)

    {

        // add lock

        while (1) {

            std::this_thread::sleep_for(std::chrono::milliseconds(1000));

            std::unique_lock<std::mutex> l1(g_pcMutex);

            // wait

            g_pcCondition.wait(l1, [](){return g_package.size() <= MAX_NUM;});

            // exec

            g_nextIndex++;

            g_package.push_back(g_nextIndex);

            std::cout<<"productor thread id is:"<<id<<"g_nextIndex is:"<<g_nextIndex<<std::endl;

            std::cout<<"productor deque size is:"<<g_package.size()<<std::endl;

            // notify

            g_pcCondition.notify_all();

        }

    }

     

    void consumerFunction(int id)

    {

        while(1) {

            // create mutex

            // std::this_thread::sleep_for(std::chrono::milliseconds(500));

            std::this_thread::sleep_for(std::chrono::milliseconds(1000));

            std::unique_lock<std::mutex> l2(g_pcMutex);

            // wait

            g_pcCondition.wait(l2, [](){std::cout<<"consumer wait"<<std::endl; return g_package.size() <= 2;});

            // g_pcConditon--

            g_nextIndex--;

            // deque pushfront

            g_package.pop_back();

            // print

            std::cout<<"consumer thread id is:"<<id<<"g_nextIndex is:"<<g_nextIndex<<std::endl;

            std::cout<<"consumer deque size is:"<<g_package.size()<<std::endl;

            // notify

            g_pcCondition.notify_all();

        }

    }

     

    int main(int argc, char *argv[])

    {

        QApplication a(argc, argv);

        std::thread consumerThread[MAX_THREAD];

        std::thread productorThread[MAX_THREAD];

        for (int i=0;i<MAX_THREAD;i++) {

            consumerThread[i] = std::thread(consumerFunctioni);

        }

        for(int i=0;i<MAX_THREAD;i++) {

            productorThread[i] = std::thread(productorFunctioni);

        }

     

        for (int i=0;i<MAX_THREAD;i++) {

            consumerThread[i].join();

        }

     

        for (int i=0;i<MAX_THREAD;i++) {

            productorThread[i].join();

        }

        MainWindow w;

        w.show();

     

        return a.exec();

    }

     

    这是一个消费者和管理者的典型问题。我这里要说的只有一个:g_pcCondition.wait(l2, [](){std::cout<<"consumer wait"<<std::endl; return g_package.size() <= 2;}); 

    这句话的意思就是小于2执行,大于2不执行,这就保证里面最少两个值。

    另外,针对线程的问题一定要删除重新编译,否则会出现莫名奇妙的问题。

  • 相关阅读:
    Django REST framework+Vue 打造生鲜超市(十一)
    Django REST framework+Vue 打造生鲜超市(十)
    Django REST framework+Vue 打造生鲜超市(九)
    Django REST framework+Vue 打造生鲜超市(八)
    SVN服务器搭建和使用(二)
    SVN服务器搭建和使用(一)
    web前端性能优化
    js数组去重
    常见的字符串隐式转换
    js中this的用法
  • 原文地址:https://www.cnblogs.com/songyuchen/p/14453605.html
Copyright © 2011-2022 走看看