• 关于Condition Variable的一些思考


    可能大家都使用过condition variable(之后称cv),一些博客也对cv做了介绍,但是有的说的不完全正确,甚至有误导使用者的倾向,其实最合理的使用方式是查阅文档,

    如果你英语还ok的话,http://en.cppreference.com/w/cpp/thread/condition_variable 读一遍即可 完全没有必要看我写的这些砖块,但是我的blog可能思路上更贴近初学者,可以借鉴一下。

    背景不过多做介绍,使用condition variable的时候都要配合mutex使用,那么mutex就是为了什么呢,为什么要用这个东西呢?

    为什么要这样设计?mutex多余吗?他是用来保护什么数据呢?

    ok!让我们来从零开始。

    condition variable的初衷是为了wait某一个东东变成true或false,然后此线程继续进行。

    按照此需求,大概的代码应该是这样:

    //version 1.0
    Thread 1:
    cv.wait();
    Thread 2:
    cv.signal();

    这样的话有一个很严重的问题,如果t2先执行,那么t1岂不是永远停在那里了!这样与我们最初的构想完全不符!

    于是产生了下面的写法:

    //version 2.0
    bool signaled = false;
    thread 1if(!signaled)
      cv.wait();
    thread 2:
    cv.signal();
    signaled = true;

    看上去不错吧!但是犯了一个很白痴的错误,bool并不是thread safe的,所以

    //version 2.1
    bool signaled = false;
    thread 1
    lock; if(!signaled) //1
      unlock;   cv.wait(); //2 thread 2: cv.signal(); //3
    lock; signaled = true; //4
    unlock;

    这样我们把signaled保护起来,保证thread safe了,会有什么问题吗?

    当然有问题!如果执行顺序是这样的话,3-1-4-2 那么thread 1永远wait在那里了!

    欸!为什么会这样呢!让我们从长计议!一开始为了保证signal是否发出,我们增加了一个标志位来判断是否信号已经发出,如果已经发出,那么cv不需要等待。接着,我们对thread safe进行了保证,然后现在又出现了执行顺序的问题!

    仔 细思考一下,就会发现,其实导致问题的原因就在于需求的原子性并没有很好的用c++表达出来,你想想,thread 1的意思翻译成汉语就是“如果没有信号没有触发,那么我就等”,thread 2的意思很明显就是“触发!”,但是触发一句话到了c++语义中就变成了两个语句,多个指令,那么你很难满足我的需求,至少在不进行原子性语句约束的情况 下你无法满足我的需求。

    所以 thread 1 这段代码就是一团糟!当进行wait操作之前,你能确定signaled为false吗?答案是否定的。

    或者说thread 2 这段代码 当你signal之后,你的signaled马上变成true了吗? 答案也是否定的,你如果无法保证这两个语句的原子性,那么就无法达到需求。

    所以我们继续修改:

    //version 3.0
    bool signaled = false;
    thread 1:
    lock;
    if(!signaled) //1
      cv.wait(); //2
    unlock;
    thread 2:
    lock;
    cv.signal(); //3 signaled = true; //4
    unlock;

    这样就ok了!但是如果你听说过spurious wakeup(可以自行查阅一下),那么3.0依然是不够完善的,

    最终的版本是:

    //version 4.0
    bool signaled = false;
    thread 1:
    lock;
    while(!signaled) //1
      cv.wait(); //2
    unlock;
    thread 2:
    lock;
    cv.signal(); //3
    signaled = true; //4
    unlock;

    然后我们回到最初的问题上去,为什么需要传入一个参数mutex,答案就很显然了,是为了让cv进行unlock操作的,当cv拿回线程的权杖的时候,再把mutex lock上,保证对称性。

  • 相关阅读:
    Linux基础优化(二)
    权限
    分页
    序列化
    forms
    redis
    Django缓存机制
    跨域问题
    Django的ORM
    模板层
  • 原文地址:https://www.cnblogs.com/houhoujun/p/4392870.html
Copyright © 2020-2023  润新知