使用条件变量的那些坑,你知道吗?

【时间管理的本质是目的管理,如果我们想要更好的利用时间,最先要做的是找到最值得我们花费时间的事情,自我学习和精进才是最值得我们做的有意义的事。】

想必大家开发过程中都会用到多线程,用到多线程基本上都会用到条件变量,你理解的条件变量只是简单的wait和notify吗,最近工作中看同事也都只是简单的使用wait和notify,导致项目出现bug却不知如何fix bug,其实这里面还是有一些坑的,程序喵这里总结给大家。

本文内容简介:

  • 什么是条件变量?
  • 条件变量如何使用?
  • 如何解决条件变量的信号丢失问题?
  • 如何解决条件变量的虚假唤醒问题?
  • 条件变量为什么一定要和锁配合使用?

什么是条件变量

条件变量是多线程程序中用来实现等待和唤醒逻辑常用的方法。通常有wait和notify两个动作,wait用于阻塞挂起线程A,直到另一个线程B通过通过notify唤醒线程A,唤醒后线程A会继续运行。
条件变量在多线程中很常用,在有名的生产者和消费者问题中,消费者如何知道生成者是否生产出了可以消费的产品,通过while循环不停的去判断是否有可消费的产品?众所周知,死循环极其消耗CPU性能,所以需要使用条件变量来阻塞线程,降低CPU占用率。

条件变量的使用

拿生产者和消费者问题举例,看下面这段代码:

std::mutex mutex;
std::condition_variable cv;
std::vector<int> vec;

void Consume() {
    std::unique_lock<std::mutex> lock(mutex);
    cv.wait(lock);
    std::cout << "consume " << vec.size() << "\n";
}

void Produce() {
    std::unique_lock<std::mutex> lock(mutex);
    vec.push_back(1);
    cv.notify_all();
    std::cout << "produce \n";
}

int main() {
    std::thread t(Consume);
    t.detach();
    Produce();
    return 0;
}

本意是消费者线程阻塞,等待生产者生产数据后去通知消费者线程,这样消费者线程就可以拿到数据去消费。
但这里有个问题:如果先执行的Produce(),后执行的Consume(),生产者提前生产出了数据,去通知消费者,但是此时消费者线程如果还没有执行到wait语句,即线程还没有处于挂起等待状态,线程没有等待此条件变量上,那通知的信号就丢失了,后面Consume()中才执行wait处于等待状态,但此时生产者已经不会再触发notify,那消费者线程就会始终阻塞下去,出现bug
如何解决这个问题呢?可以附加一个判断条件,就可以解决这种信号丢失问题,见代码:

std::mutex mutex;
std::condition_variable cv;
std::vector<int> vec;

void Consumer() {
    std::unique_lock<std::mutex> lock(mutex);
    if (vec.empty()) { // 加入此判断条件
        cv.wait(lock);
    }
    std::cout << "consumer " << vec.size() << "\n";
}

void Produce() {
    std::unique_lock<std::mutex> lock(mutex);
    vec.push_back(1);
    cv.notify_all();
    std::cout << "produce \n";
}

int main() {
    std::thread t(Consumer);
    t.detach();
    Produce();
    return 0;
}

通过增加附加条件可以解决信号丢失的问题,但这里还有个地方需要注意,消费者线程处于wait阻塞状态时,即使没有调用notify,操作系统也会有一些概率会唤醒处于阻塞的线程,使其继续执行下去,这就是虚假唤醒问题,当出现了虚假唤醒后,消费者线程继续执行,还是没有可以消费的数据,出现了bug。
那怎么解决虚假唤醒的问题呢,可以在线程由阻塞状态被唤醒后继续判断附加条件,看是否满足唤醒的条件,如果满足则继续执行,如果不满足,则继续去等待,体现在代码中,即将if判断改为while循环判断,见代码:

std::mutex mutex;
std::condition_variable cv;
std::vector<int> vec;

void Consumer() {
    std::unique_lock<std::mutex> lock(mutex);
    while (vec.empty()) { // 将if改为while
        cv.wait(lock);
    }
    std::cout << "consumer " << vec.size() << "\n";
}

void Produce() {
    std::unique_lock<std::mutex> lock(mutex);
    vec.push_back(1);
    cv.notify_all();
    std::cout << "produce \n";
}

int main() {
    std::thread t(Consumer);
    t.detach();
    Produce();
    return 0;
}

看到这里您应该已经明白条件变量的使用啦,需要使用while循环附加判断条件来解决条件变量的信号丢失和虚假唤醒问题。

有没有更简单的方式

难道我们每次都需要使用while循环和附加条件来操作条件变量吗,这岂不是很麻烦,在C++中其实有更好的封装,只需要调用wait函数时在参数中直接添加附加条件即可,内部已经做好了while循环判断,直接使用即可,见代码:

std::mutex mutex;
std::condition_variable cv;
std::vector<int> vec;

void Consumer() {
    std::unique_lock<std::mutex> lock(mutex);
    cv.wait(lock, [&](){ return !vec.empty(); }); // 这里可以直接使用C++的封装
    std::cout << "consumer " << vec.size() << "\n";
}

void Produce() {
    std::unique_lock<std::mutex> lock(mutex);
    vec.push_back(1);
    cv.notify_all();
    std::cout << "produce \n";
}

int main() {
    std::thread t(Consumer);
    t.detach();
    Produce();
    return 0;
}

但在C语言中就没办法啦,大家只能自己做一层封装啦。

为什么条件变量需要和锁配合使用

为什么叫条件变量呢?因为内部是通过判断及修改某个全局变量来决定线程的阻塞与唤醒,多线程操作同一个变量肯定需要加锁来使得线程安全。同时一个简单的wait函数调用内部会很复杂的,有可能线程A调用了wait函数但是还没有进入到wait阻塞等待前,另一个线程B在此时却调用了notify函数,此时nofity的信号就丢失啦,如果加了锁,线程B必须等待线程A释放了锁并进入了等待状态后才可以调用notify,继而防止信号丢失。

关于条件变量就介绍到这里,希望大家能有所收获,平时使用过程中可以避掉条件变量的坑。
更多文章,请关注我的V X 公 主 号:程序喵大人,欢迎交流。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,941评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,397评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,345评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,851评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,868评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,688评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,414评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,319评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,775评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,945评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,096评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,789评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,437评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,993评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,107评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,308评论 3 372
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,037评论 2 355