今天,我写了一篇关于条件变量的恐怖文章。您应该意识到条件变量的这一问题。C ++核心准则CP 42仅声明:“不要无条件等待”。

等待!条件变量支持一个非常简单的概念。一个线程准备一些东西并发送通知,另一个线程正在等待。为什么不能这么危险?好吧,让我们从今天的唯一规则开始。

这是该规则的基本原理:“没有条件的等待可能会错过唤醒或仅醒来就发现没有工作要做。” 这意味着什么?条件变量可能是两个非常严重的问题的受害者:唤醒丢失和伪唤醒。关于条件变量的关键问题是它们没有记忆(memory)。

在我向您介绍此问题之前,请先让我正确进行操作。这是模式,如何使用条件变量。

// conditionVariables.cpp

#include

#include

#include

std::mutex mutex_;

std::condition_variable condVar;

bool dataReady{false};

void waitingForWork(){

std::cout << "Waiting " << std::endl;

std::unique_lock<:mutex> lck(mutex_);

condVar.wait(lck, []{ return dataReady; });  // (4)

std::cout << "Running " << std::endl;

}

void setDataReady(){

{

std::lock_guard<:mutex> lck(mutex_);

dataReady = true;

}

std::cout << "Data prepared" << std::endl;

condVar.notify_one();                        // (3)

}

int main(){

std::cout << std::endl;

std::thread t1(waitingForWork);              // (1)

std::thread t2(setDataReady);                // (2)

t1.join();

t2.join();

std::cout << std::endl;

}

同步如何工作?该程序有两个子线程:t1和t2。他们 在第(1和2)行中获得了工作包 waitingForWork 和setDataRead。setDataReady 通知-使用条件变量condVar -即它与工作的准备完成:condVar.notify_one() (第3行)。在持有锁的同时,线程t1 等待其通知:condVar.wait(lck,[] {return dataReady;})(第4行)。发送者和接收者需要锁。对于发送者,则为std :: lock_guard 足够了,因为它只调用一次锁定和解锁。对于接收器,std :: unique_lock 是必需的,因为它通常会频繁地锁定和解锁其互斥锁。

这是程序的输出。

也许您在想:为什么您需要一个谓词才能进行wait调用,因为您可以在 没有谓词的情况下调用wait?对于如此简单的线程同步,此工作流程似乎过于复杂。

现在我们回到memory的丢失中,这两种现象称为丢失唤醒和伪唤醒。

丢失的唤醒和虚假的唤醒

唤醒丢失:唤醒丢失的现象是发送方在接收方进入其等待状态之前发送其通知。结果是通知丢失。C ++标准描述条件变量作为同时同步机制:"The condition_variable class is a synchronisation primitive that can be used to block a thread, or multiple threads at the same time, ..."。因此通知丢失了,接收方正在等待,并且等待...。

虚假唤醒:尽管没有发送通知,但接收器可能会唤醒。至少,POSIX Threads 和Windows API可能成为这些现象的受害者。

为了不成为这两个问题的受害者,您必须使用其他谓词作为记忆(memory)。或按规则规定是附加条件。如果您不相信,这里就是等待工作流程。

等待工作流程

在等待的初始处理中,线程将锁定互斥锁,然后检查谓词[] {return dataReady;。}。

如果谓词的调用评估为

true:线程继续其工作。

false:condVar.wait() 解锁互斥锁并将线程置于等待(阻塞)状态

如果condition_variable condVar处于等待状态并收到通知或虚假唤醒,则会发生以下步骤。

线程被解除阻止,并将重新获取互斥锁。

线程检查谓词。

如果谓词的调用评估为

true:线程继续其工作。

false:condVar.wait()解锁互斥锁,并将线程置于等待(阻塞)状态。

复杂!对?你不相信我吗

没有谓词

如果我从上一个示例中删除谓词,将会发生什么?

// conditionVariableWithoutPredicate.cpp

#include

#include

#include

std::mutex mutex_;

std::condition_variable condVar;

void waitingForWork(){

std::cout << "Waiting " << std::endl;

std::unique_lock<:mutex> lck(mutex_);

condVar.wait(lck);                      // (1)

std::cout << "Running " << std::endl;

}

void setDataReady(){

std::cout << "Data prepared" << std::endl;

condVar.notify_one();                  // (2)

}

int main(){

std::cout << std::endl;

std::thread t1(waitingForWork);

std::thread t2(setDataReady);

t1.join();

t2.join();

std::cout << std::endl;

}

现在,第(1)行中的wait调用不使用谓词,并且同步看起来非常容易。遗憾地说,但现在的程序有一个竞争条件,你可以在第一个执行看到。屏幕截图显示了死锁。

发送方在接收方能够接收之前,在第(1)行(condVar.notify_one())中发送其通知;因此,接收器将永远休眠。

好的,教训是艰难的。谓词是必要的,但必须有一种方法可以简化程序的条件。

原子谓词

也许您已经看到了。变量dataReady只是一个布尔值。我们应该将其设为原子布尔值,并因此摆脱发送方上的互斥量。

我们来了:

// conditionVariableAtomic.cpp

#include

#include

#include

#include

std::mutex mutex_;

std::condition_variable condVar;

std::atomic dataReady{false};

void waitingForWork(){

std::cout << "Waiting " << std::endl;

std::unique_lock<:mutex> lck(mutex_);

condVar.wait(lck, []{ return dataReady.load(); });  // (1)

std::cout << "Running " << std::endl;

}

void setDataReady(){

dataReady = true;

std::cout << "Data prepared" << std::endl;

condVar.notify_one();

}

int main(){

std::cout << std::endl;

std::thread t1(waitingForWork);

std::thread t2(setDataReady);

t1.join();

t2.join();

std::cout << std::endl;

}

与第一个版本相比,该程序非常简单,因为dataReady不必由互斥量保护。程序再次处于竞争状态,可能导致死锁。为什么?dataReady是原子的!是的,但是第(1)行中的wait表达式(condVar.wait(lck,[] {return dataReady.load();});)比看起来复杂得多。

wait表达式等效于以下四行:

std :: unique_lock < std ::互斥> lck(mutex_);

while(! [] { return dataReady.load();}(){

//时间窗口(1)    condVar.wait(lck);}

即使将dataReady设为原子,也必须在互斥锁下对其进行修改;如果不是,则可能会发布对等待线程的修改,但不能正确同步。这种竞争状况可能会导致死锁。这是什么意思:已发布但未正确同步。让我们仔细看一下前面的代码片段,并假设数据是原子的,并且不受互斥对象Mutex_的保护。

让我假设在条件变量condVar在等待表达式中但不在等待状态时发送通知。这意味着线程的执行位于注释时间窗口(第1行)所在行的源代码片段中。结果是通知丢失。之后,线程返回等待状态,并且可能永远休眠。

如果dataReady受互斥锁保护,则不会发生这种情况。由于与互斥锁同步,因此仅在条件变量(因此接收方线程)处于等待状态时才发送通知。

多么恐怖的故事?有没有可能使用开始的程序conditionVariables.cpp更容易?不,不是带有条件变量的,但是您可以使用promise和future配对来使工作完成

php 语法 条件变量,C ++核心准则:注意条件变量的陷阱相关推荐

  1. Kotlin学习(二)—— 基本语法,函数,变量,字符串模板,条件表达式,null,类型检测,for,while,when,区间,集合

    一.基本语法 Kotlin的很多概念跟JAVA是有类似的,所以我应该不会像我的JAVA之旅一样那么的详细,但是不用担心,你会看的很明白的,我也是根据官方的文档来学习的 我们在IDEA中创建一个项目Ko ...

  2. python列表嵌套字典取值_Python基础语法:你不得不知的几种变量类型

    (点击上方快速关注并设置为星标,一起学Python) 作者:kina_chen來源:简书 01. Python编码Python中默认的编码格式是 ASCII 格式,在没修改编码格式时无法正确打印汉字, ...

  3. Pyhton基础篇(2)-变量、用户输入及条件语句(已更新)

    1. 变量 Python中的变量概念很好理解,变量其实就是某个数值的"名字". 变量定义的规则: (1)   只能由数字.字母.下划线组成(不能以数字开头) (2)   不能使用关 ...

  4. modbus软件开发实战指南_C++核心准则?GSL:指南支持库

    GSL: Guidelines support library GSL:指南支持库 The GSL is a small library of facilities designed to suppo ...

  5. python变量声明语句_python – 在条件语句中声明变量有问题吗?

    在条件的所有可能分支中重新定义变量之前,它是否可以防止定义变量? 比如应该这个代码: # Condition could fail try: textureIndices = someExpressi ...

  6. 开源压缩算法brotli_Google的Brotli压缩算法,C ++核心准则以及更多新闻

    开源压缩算法brotli 在本周的开源新闻综述中,我们将介绍Google的Brotli压缩算法,适用于GitHub的Classroom,C ++ Core Guidelines等! 2015年9月20 ...

  7. [C++11 多线程同步] --- 条件变量的那些坑【条件变量信号丢失和条件变量虚假唤醒(spurious wakeup)】

    1 条件变量的信号丢失 1.1 条件变量的信号丢失场景重现 拿生产者和消费者模型举例,看一段示例代码: #include <iostream> #include <vector> ...

  8. Excel VLOOKUP实用教程之 04 vlookup如何实现三变量查找,三个条件字段查询数据?(教程含数据excel)

    实战需求 vlookup如何实现三变量查找,三个条件字段查询数据? 文章目录 <示例 1 – 查找 Brad 的数学分数> <示例 2 – 双向查找> <示例 3 – 使 ...

  9. if test 多条件_五条写好JavaScript条件语句的建议(译)

    1. 多重准则时使用 Array.includes 看个栗子: function test(fruit) {if (fruit == 'apple' || fruit == 'strawberry') ...

最新文章

  1. 常考的 21 条 Linux 命令
  2. abaqus画一个球 python_简单几步,100行代码用Python画一个蝙蝠侠的logo
  3. Java反射_JDBC操作数据
  4. redhat6.x_linux学习笔记
  5. Effective Java之考虑用序列化代理代理序列化实例(七十八)
  6. 【LeetCode】3月25日打卡-Day10
  7. python弹出框多一个空白框_Selenium+python3 应对多个弹出框存在(alert_is_present)判断和处理...
  8. atom芯片和服务器芯片冲突,大小核混合X86可否在笔记本市场与Arm一战?英特尔Atom再昂首先前...
  9. ZetCode JavaScript 教程
  10. python 进行后端分页详细代码
  11. js轮播图 最简单代码
  12. HMI 排行中有哪些触摸屏品牌
  13. 【从0开始音乐demo的制作:预计耗时15小时(一)】项目创建和Vue 3.x vue-cli 的选项问题
  14. 西门子G120调试参数设置
  15. Android 系统字体
  16. ts中简单的用法和存储器 get set 的用法
  17. Android应用上架国内各大应用市场对应用Logo、应用截图要求整理
  18. 【Suffix Array】后缀数组详解
  19. 【ArcGIS Server】切片时C盘空间不足(本地缓存目录空间不足)
  20. 江枫谈淘宝“双十一”事件中的数据库架构优化

热门文章

  1. PHP安装包中VC9和VC11的含义
  2. android 中的aidl
  3. 关于img图片的onerror属性
  4. Blackberry阻碍因素
  5. 什么是延迟?怎样解决?—Vecloud微云
  6. DHCP和DNS的概念—Vecloud微云
  7. Kuebernetes之DaemonSet
  8. 复用io selectors模块
  9. C++程序的多文件组成
  10. mysql建表以及列属性