关于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不执行,这就保证里面最少两个值。

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

原文地址:https://www.cnblogs.com/songyuchen/p/14453605.html