之前一段时间偶然在 B 站上刷到了南京大学蒋炎岩(jyy)老师在直播操作系统网课。点进直播间看了一下发现这个老师实力非凡,上课从不照本宣科,而且旁征博引又不吝于亲自动手演示,于是点了关注。后来开始看其网课录播,其中一节的标题吸引了我,多处理器编程:从入门到放弃 (线程库;现代处理器和宽松内存模型)。“多处理器编程”这个词让我联想到去年看的《The Art of Multiprocessor Programming》,于是仔细看了一下这节网课。里面介绍到了一个试例 write_x_read_y,它是用 C 语言和内联汇编写的,它用来说明运行期指令重排。这个试例能够成功观测到运行期指令重排现象。这让我不得不佩服 jyy 的实践精神。之前看了一些介绍 C++ 内存模型的文章,没有一个能用可复现的完整代码说明问题的,全部都是说这段代码可能出现 xx 结果,没有实际的执行结果。在 C++ 内存模型中,这个测试用例除了能够说明运行期指令重排,也能用于说明 happens-before consistency 和 sequential consistency 的差别。于是尝试用 C++ Atomic 来实现这段代码,看看能不能观测到预期结果。

首先线程库 pthread 替换为 std::thread,内联汇编替换为 std::atomic,且 load 和 store 操作全部使用最弱的 std::memory_order_relaxed 内存序。完整的代码如下:

// write_x_read_y.cpp

#include <atomic>
#include <thread>
#include <stdio.h> static std::atomic_int flag{0}; inline void wait_flag(int id)
{
while (!(flag & (0x1 << id))) {}
} inline void clear_flag(int id)
{
flag.fetch_and(~(0x1 << id));
} std::atomic_int x{0}, y{0}; void write_x_read_y()
{
while (true) {
wait_flag(0); x.store(1, std::memory_order_relaxed); // t1.1
int v = y.load(std::memory_order_relaxed); // t1.2
printf("%d ", v); clear_flag(0);
}
} void write_y_read_x()
{
while (true) {
wait_flag(1); y.store(1, std::memory_order_relaxed); // t2.1
int v = x.load(std::memory_order_relaxed); // t2.2
printf("%d ", v); clear_flag(1);
}
} int main()
{
std::thread t1(write_x_read_y), t2(write_y_read_x); while (true) {
x = 0, y = 0;
flag = 0b11; while (flag) {} printf("\n");
fflush(stdout);
} t1.join();
t2.join();
}

注意这段代码要开启代码优化才能观测到运行期指令重排,这里选择 O2

g++ -o write_x_read_y.out -O2 -pthread -std=c++11 -Wall -Wextra write_x_read_y.cpp

然后使用 jyy 视频里使用的 Unix 命令进行测试并整理结果

./write_x_read_y.out | head -n1000000 | sort | uniq -c

以下结果是在虚拟机环境中执行得到的。宿主机 CPU 型号为 AMD Ryzen 7 5800X,OS 为 Windows 10 x64,虚拟机是 Rocky Linux 8.6。

 948739 0 0
50150 0 1
1109 1 0
2 1 1

成功观测到“0 0”。假设程序按照简单交叉执行,执行结果只可能是“0 1”、“1 0”、“1 1”这三种,不可能出现“0 0”。也就是说发生了运行期指令重排。

接下来,将 std::memory_order_relaxed 替换为 std::memory_order_releasestd::memory_order_acquire,再测一遍

x.store(1, std::memory_order_release);    // t1.1
int v = y.load(std::memory_order_acquire); // t1.2
printf("%d ", v); y.store(1, std::memory_order_release); // t2.1
int v = x.load(std::memory_order_acquire); // t2.2
printf("%d ", v);

测试结果为:

 613684 0 0
360557 0 1
25757 1 0
2 1 1

又出现了“0 0”,也就说明这个试例无法区分 relaxed memory model 和 happens-before consistency。这也与理论相符,虽然 t1.1 happens-before t2.2、t2.1 happens-before t1.2,但是却无法借此推导出约束关系来限制执行结果。“0 0”依然有可能出现。

接下来替换为 std::memory_order_seq_cst

x.store(1, std::memory_order_seq_cst);    // t1.1
int v = y.load(std::memory_order_seq_cst); // t1.2
printf("%d ", v); y.store(1, std::memory_order_seq_cst); // t2.1
int v = x.load(std::memory_order_seq_cst); // t2.2
printf("%d ", v);

测试结果为:

 132394 0 1
151 1 0
867455 1 1

这次“0 0”并没有出现,运行期指令重排没有被观测到。这与理论相符,使用 std::memory_order_seq_cst 的所有原子操作可以视为简单交叉执行,也就是 sequential consistency。“0 0”不可能出现。

write_x_read_y 这个试例很好地说明了 C++ 内存模型中的 happens-before consistency 和 sequential consistency 的区别。它的代码片段常见于各种相关文章中,却没有完整的代码和实际的测试结果。这下也算补全了 C++ 内存模型知识的一块拼图。

C++ 内存模型 write_x_read_y 试例构造的更多相关文章

  1. 并发编程之 Java 内存模型 + volatile 关键字 + Happen-Before 规则

    前言 楼主这个标题其实有一种作死的味道,为什么呢,这三个东西其实可以分开为三篇文章来写,但是,楼主认为这三个东西又都是高度相关的,应当在一个知识点中.在一次学习中去理解这些东西.才能更好的理解 Jav ...

  2. java内存模型及分块

    转自:http://www.cnblogs.com/BangQ/p/4045954.html 1.JMM简介 2.堆和栈 3.本机内存 4.防止内存泄漏   1.JMM简介   i.内存模型概述 Ja ...

  3. Inside JVM 内存模型

    Inside JVM 内存模型 来源  原文:https://blog.csdn.net/silentbalanceyh/article/details/4661230 参考:IBM开发中心文档,&l ...

  4. Java内存模型(转载)

    本文章节: 1.JMM简介 2.堆和栈 3.本机内存 4.防止内存泄漏 1.JMM简介 i.内存模型概述 Java平台自动集成了线程以及多处理器技术,这种集成程度比Java以前诞生的计算机语言要厉害很 ...

  5. JVM内存模型 三

    本文章节: 1.JMM简介 2.堆和栈 3.本机内存 4.防止内存泄漏   1.JMM简介 i.内存模型概述 Java平台自动集成了线程以及多处理器技术,这种集成程度比Java以前诞生的计算机语言要厉 ...

  6. 第三章 - CPU缓存结构和java内存模型

    CPU 缓存结构原理 CPU 缓存结构 查看 cpu 缓存 速度比较 查看 cpu 缓存行 cpu 拿到的内存地址格式是这样的 CPU 缓存读 根据低位,计算在缓存中的索引 判断是否有效 0 去内存读 ...

  7. Java内存模型深度解析:final--转

    原文地址:http://www.codeceo.com/article/java-memory-6.html 与前面介绍的锁和Volatile相比较,对final域的读和写更像是普通的变量访问.对于f ...

  8. 【JVM】JVM系列之内存模型(六)

    一.前言 经过前面的学习,我们终于进入了虚拟机最后一部分的学习,内存模型.理解内存模型对我们理解虚拟机.正确使用多线程编程提供很大帮助.下面开始正式学习. 二.Java并发基础 在并发编程中存在两个关 ...

  9. JVM内存模型、指令重排、内存屏障概念解析

    在高并发模型中,无是面对物理机SMP系统模型,还是面对像JVM的虚拟机多线程并发内存模型,指令重排(编译器.运行时)和内存屏障都是非常重要的概念,因此,搞清楚这些概念和原理很重要.否则,你很难搞清楚哪 ...

随机推荐

  1. 探索ABP基础架构-下

    配置应用程序 ASP.NET Core 的配置系统提供了一个基于键值对的配置方法.它是一个可扩展的系统,可以从各种资源中读取键值对,例如 JSON 设置文件.环境变量.命令行参数等等. 设置配置值 默 ...

  2. python初识数据类型(字典、集合、元组、布尔)与运算符

    目录 python数据类型(dict.tuple.set.bool) 字典 集合 元组 布尔值 用户交互与输出 获取用户输入 输出信息 格式化输出 基本运算符 算术运算符 比较运算符 逻辑运算符 赋值 ...

  3. 安装Speedtest到CentOS(YUM)

    Speedtest是一个由Python语言编写的一个网络测速脚本,提供多个外网的测试站点,我们可以使用它测试网络的IO速度. 如果由于网络问题导致无法下载软件包,则可以通过安装模块到Python的方式 ...

  4. 每天一个 HTTP 状态码 203

    203 Non-Authoritative Information 203 Non-Authoritative Information 'Non-Authoritative Informative' ...

  5. 05-STL

    Day01 笔记 1 STL概论 1.1 STL六大组件 1.1.1 容器.算法.迭代器.仿函数.适配器.空间配置器 1.2 STL优点 1.2.1 内建在C++编译器中,不需要安装额外内容 1.2. ...

  6. CabloyJS的微信API对接模块:当前支持微信公众号和微信小程序

    Cabloy-微信是什么 Cabloy-微信是基于CabloyJS全栈业务开发框架开发的微信接口模块,当前整合了微信公众号和微信小程序的接口,达到开箱即用的使用效果.在Cabloy-微信的基础上,可以 ...

  7. 互联网公司目标管理OKR和绩效考核的误区

    最近看了一篇关于「谷歌放弃OKR,转向全新的GRAD系统」的文章,我转到了研发效能DevOps的微信群里,结果引起了大家热烈的讨论,正好我们也在使用 OKR,所以也来谈谈我的理解以及我们应用起来的实际 ...

  8. camunda BPM支持的开发和运行环境

    以Camunda7.13版本为例,介绍Camunda支持的开发运行环境. 一.支持的Java开发环境 • Java版本: 8 / 9 / 10 / 11 / 12 / 13 / 14• Springb ...

  9. ExtJS 布局-Center布局(Center layout)

    更新记录: 2022年6月12日 检查发布. 2022年6月1日 开始. 1.说明 center布局将子项放置在容器的中心. 2.设置布局方法 在父容器中设置 layout: 'center', // ...

  10. TypeScript(4)接口

    介绍 TypeScript 的核心原则之一是对值所具有的结构进行类型检查.我们使用接口(Interfaces)来定义对象的类型.接口是对象的状态(属性)和行为(方法)的抽象(描述) 接口初探 声明接口 ...