内容简介:1)
目录
3. volatile 应用场景 3
4. 内存屏障( Memory Barrier ) 4
1) 结果 1 (非优化编译: g++ -g -o x x.cpp -O0 ) 5
2) 结果 2 (优化编译: g++ -g -o x x.cpp -O2 ) 6
6. 不同 CPU 架构的一致性模型 6
8. C++ 标准库对内存顺的支持 7
附 1 : CPU 、缓存和主存 8
第三级缓存( L3 Cache )多核共享: 8
附 2 : SMP 对称多处理器结构 9
1) C++ 标准委员会( The C++ Standards Committee ) 10
4) Linux 内核关于 volatile 的说明 10
5) Intel 内存模型( Intel Memory Model ) 10
6) Intel TSO 内存模型 10
7) Sequential Consistency &TSO 10
9) x86-64 和 IA-32 开发手册 10
10) 编译器屏障( Compiler Barriers ) 10
11) C ++ 11 中 memory_order_consume 的作用 10
12) MESI (多核 CPU 缓存一致性协议) 10
13) MESIF (多核 CPU 缓存一致性协议) 10
1.
本文内容主要针对 Linux ,而且主要是 x86 环境。先看一常见用法:
class Thread {
public:
X()
: _stop (false) {
}
void stop() {
_stop = true;
}
void run() {
while (! _stop ) {
work();
}
}
private:
volatile bool _stop ;
};
然后看看 标准 C++ 基金会 ( https://isocpp.org )怎么说的( 官方链接 ):
2. 结论
1) 与平台无关的多线程程序, volatile 几乎无用( Java 和 C# 中的 volatile 除外);
2) volatile 不保证原子性(一般需使用 CPU 提供的 LOCK 指令);
3) volatile 不保证执行顺序;
4) volatile 不提供内存屏障( Memory Barrier )和内存栅栏( Memory Fence );
5) 多核环境中内存的可见性和 CPU 执行顺序不能通过 volatile 来保障,而是依赖于 CPU 内存屏障。
注: volatile 诞生于单 CPU 核心时代,为保持兼容,一直只是针对编译器的,对 CPU 无影响。
volatile 在 C/C++ 中的作用:
1) 告诉编译器不要将定义的变量优化掉;
2) 告诉编译器总是从缓存取被修饰的变量的值,而不是寄存器取值。
就前言中的代码,可移植的实现方式为:
#include< atomic >
class Thread {
public:
X()
: _stop (false) {
}
void stop() {
_stop = true;
}
void run() {
while (! _stop ) {
work();
}
}
private:
std::atomic< bool > _stop ;
};
不过这要求至少 C++11 ,否则可使用兼容 C++98 的实现 CAtomic
https://github.com/eyjian/libmooon/blob/master/include/mooon/sys/atomic.h ,实际上 Linux 内核源代码都带有这些基础设施。
3. volatile 应用场景
1) 信号处理程序;
2) 与硬件打交道(嵌入式开发用得多);
3) 和 setjmp 、 longjmp 配合(请参见: http://www.cplusplus.com/reference/csetjmp/setjmp/ ),原因同信号处理。
4. 内存屏障( Memory Barrier )
内存屏障,也叫内存栅栏( Memory Fence )。分编译器屏障( Compiler Barrier ,也叫优化屏障)和 CPU 内存屏障,其中编译器屏障只对编译器有效,它们的定义如下表所示(限 x86 ,其它架构并不相同):
#define barrier() \ __asm__ __volatile__("":::"memory") |
编译器屏障,如果是GCC,则可用__sync_synchronize()替代 |
#define mb() \ __asm__ __volatile__(" mfence ":::"memory") |
CPU内存屏障,还分读写内存屏障 |
#define rmb() \ __asm__ __volatile__(" lfence ":::"memory") |
CPU读(Load)内存屏障 |
#define wmb() \ __asm__ __volatile__(" sfence ":::"memory") |
CPU写(Store)内存屏障 |
x86 架构的 CPU 内存屏障代码可在内核源代码的 arch/x86/include/asm/barrier.h 中找到。对于原子操作,需要使用 CPU 提供的“ lock ”指令,对于 CPU 乱序需使用 CPU 内存屏障。
代码顺序 |
编译器顺序 |
CPU顺序 |
a=1; b=x; c=z; d=2; |
a=1; d=2; b=x; c=z; |
b=x; c=z; a=1; d=2; |
推荐资料:
https://mariadb.org/wp-content/uploads/2017/11/2017-11-Memory-barriers.pdf
内存屏障进一步还分读内存屏障和写内存屏障等,对非内核开发者来说,内存屏障的主要应用场景为无锁编程( Lock-free ),因为像 pthread_mutx_t 等实际已经包含了“ Lock ”和“ Memory Barrier ”,所以无需再操心。
5. setjmp 和 longjmp
在 C/C++ 中, goto 关键词只能函数内的局部跳转,函数间的跳转需要使用 setjmp 和 longjmp ,这也是有些协程库基于 setjmp 和 longjmp 实现的原因。
1) setjmp
保存上下文,包括信号掩码,类似于 setcontext 。该函数的返回值比较特别,第一次返回 0 ,第二次返回的 longjmp 第二个参数值(如果 longjmp 第二个参数值为 0 ,则返回值为 1 ,这样方便区分于第一次返回)。
2) longjmp
该函数 从不返回 ,而是跳回到 setjmp 保存点,类似于 swapcontext 。如果没有先调用 setjmp ,则 longjmp 的行为是未定义的。 C++ 代码可能还会执行栈展开( Unwinding ),如果调用了任何非平凡析构函数( non-trivial destructors ,需显示处理的析构函数,如内存释放),也会导致未定义的行为。
3) 代码示例(摘自 http://www.cplusplus.com/reference/csetjmp/setjmp/ )
/* x.cpp */
/* setjmp example: error handling */
#include
#include
#include
struct X {
X() { fprintf(stderr, "X::ctor\n"); }
~X() { fprintf(stderr, "X::dtor\n"); }
};
int main()
{
jmp_buf env;
int val = -1;
int m = -1;
volatile int n = -1;
X x;
val = setjmp (env);
fprintf(stderr, "setjmp return: %d\n", val);
if (val) {
fprintf(stderr, "m: %d\n", m);
fprintf(stderr, "n: %d\n", n);
exit(val);
}
else {
m = 2018;
n = 2018;
/* code here */
longjmp (env, 19); /* signaling an error */
return 0;
}
}
上例代码运行有两种输出结果:
1) 结果 1 (非优化编译: g++ -g -o x x.cpp -O0 )
X::ctor
setjmp return: 0
setjmp return: 19
m: 2018
n: 2018
非优先编译时,总是从内存取值。
2) 结果 2 (优化编译: g++ -g -o x x.cpp -O2 )
X::ctor
setjmp return: 0
setjmp return: 19
m: -1
n: 2018
因 m 未加 volatile 修饰,直接读取寄存器值,因此结果是 -1 。从这里也可以看出,即使是单线程程序, volatile 也是必要的,也说明 volatile 并不是完全没用,只是它不能帮助解决多线程的原子性、内存屏障和 CPU 乱序执行。
另外可发现,上列代码的类 X 的析构未执行,但若将 exit 改成 return ,则会执行类 X 的析构,遇到“ } ”和“ return ”时,编译器会安插析构函数调用。
6. 不同 CPU 架构的一致性模型
注: LOAD 为 读 操作, STORE 为 写 操作。
Loads reordered after loads |
Loads reordered after stores |
Stores reordered after stores |
Stores reordered after loads |
Atomic reordered with loads |
Atomic reordered with stores |
Dependent loads reordered |
Incoherent instruction cache pipeline |
|
Alpha |
Y |
Y |
Y |
Y |
Y |
Y |
Y |
Y |
ARMv7 |
Y |
Y |
Y |
Y |
Y |
Y |
Y |
|
PA-RISC |
Y |
Y |
Y |
Y |
||||
POWER |
Y |
Y |
Y |
Y |
Y |
Y |
Y |
|
SPARC RMO |
Y |
Y |
Y |
Y |
Y |
Y |
Y |
|
SPARC PSO |
Y |
Y |
Y |
Y |
||||
SPARC TSO |
Y |
Y |
||||||
x86 |
Y |
Y |
||||||
x86 oostore |
Y |
Y |
Y |
Y |
Y |
|||
AMD64 |
Y |
|||||||
IA-64 |
Y |
Y |
Y |
Y |
Y |
Y |
Y |
|
z/Architecture |
Y |
四种 SMP 架构的 CPU 内存一致性模型:
1) 顺序一致性模型( SC , Sequential Consistency ,所有读取和所有写入都是有序的);
2) 宽松一致性模型( RC , Relaxed Consistency ,允许某些可以重排序) , ARM 和 POWER 属于这类;
3) 弱一致性模型( WC , Weak Consistency ,读取和写入任意重新排序,仅受显式内存屏障限制);
4) 完全存储排序( TSO , Total Store Ordering ), SPARC 和 x86 属于这种类型,只有“ store load ”一种情况会发生重排序,其它情况和 SC 模型一样。
7. x86-TSO
x86-TSO 是 Intel 推出的一种 CPU 内存一致性模型,特点是只有“ Store Load ”一种情况会重排序,也就是“ Load ”可以排(乱序)在“ Store ”的前面,因此不需要“ Load Load ”、“ Store Store ”和“ Load Store ”这三种屏障。
1) “ Store Load ”屏障的作用是:确保“前者刷入内存”的数据对“后者加载数据”是可见的;
2) “ Load Load ”屏障的作用是:确保“前者装载数据”先于“后者装载指令”;
3) “ Store Store ”屏障的作用是:确保“前者数据”先于“后者数据”刷入内存,且“前者刷入内存的数据”对“后者是可见的”;
4) “ Load Store ”屏障的作用是:确保“前者装载数据”先于“后者刷新数据到内存”。
8. C++ 标准库对内存顺的支持
1) 头文件
enum memory_order {
memory_order_relaxed, // 宽松一致性模型,不对执行顺序做任何保证
memory_order_consume , // (读操作)本线程所有后续有关本操作的必须在本操作完成后执行
memory_order_acquire, // (读操作)本线程所有后续的读操作必须在本条操作完成才能执行
memory_order_release, // (写操作)本线程所有之前的写操作完成后才执行本操作
memory_order_acq_rel, // (读-修改-写)同时包含Acquire和Release
memory_order_seq_cst // (读-修改-写,默认类型)顺序一致性模型,全部顺序执行
};
2) 头文件
// 默认内存顺类型为“memory_order_seq_cst”
std:: atomic
std::atomic
std::atomic
。。。。。。
附 1 : CPU 、缓存和主存
第三级缓存( L3 Cache )多核共享:
附 2 : SMP 对称多处理器结构
多个 CPU 对称工作没有区别,无主次或从属关系,平等地访问内存、外设和一个操作系统,共享全部资源,如总线、内存和 I/O 系统等,因此也被称为一致存储器访问结构( UMA : Uniform Memory Access )。
其它的架构有:
1) NUMA ( Non-Uniform Memory Access ,非统一内存访问),基本特征是将 CPU 分成多个模型,每个模型多个 CPU 组成,具有独立的本地内存和 I/O 槽口等;
2) MPP ( Massive Parallel Processing ,海量并行处理结构),基本特征是由多个 SMP 服务器(每个 SMP 服务器称节点)通过节点互联网络连接而成,每个节点只访问自己的本地资源(内存、存储等),是一种完全无共享( Share Nothing )结构。
附 3 :在线 C++ 编译器
1) https://www.tutorialspoint.com/compile_cpp_online.php
2) https://www.jdoodlecom/online-compiler-c++
3) http://coliru.stacked-crooked.com/
4) https://www.onlinegdb.com/online_c++_compiler
7) https://www.cppbuzz.com/compiler/online-c++-compiler
8) https://www.codechef.com/ide/
9) https://repl.it/repls/ZestyNaturalMatch
14) http://www.compileonline.com/
聚合了各种语言在线编译。
15) https://rextester.com/l/cpp_online_compiler_gcc
还支持其它众多语言在线编译。
附 4 :资源链接
1) C++ 标准委员会( The C++ Standards Committee )
http://www.open-std.org/jtc1/sc22/wg21/
2) 标准 C++ 基金会
3) C++ 之父
4) Linux 内核关于 volatile 的说明
https://www.kernel.org/doc/html/latest/process/volatile-considered-harmful.html
5) Intel 内存模型( Intel Memory Model )
https://en.wikipedia.org/wiki/Intel_Memory_Model
6) Intel TSO 内存模型
https://www.cl.cam.ac.uk/~pes20/weakmemory/x86tso-paper.tphols.pdf ( A Better x86 Memory Model: x86-TSO )
http://homepages.inf.ed.ac.uk/vnagaraj/papers/hpca14.pdf ( TSO-CC: Consistency directed cache coherence for TSO )
7) Sequential Consistency &TSO
https://www.cis.upenn.edu/~devietti/classes/cis601-spring2016/sc_tso.pdf
8) Write buffer
https://en.wikipedia.org/wiki/Write_buffer
9) x86-64 和 IA-32 开发手册
https://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.html ( IA-32 : Intel Architecture 32-bit ,即 32 位 x86 )
10) 编译器屏障( Compiler Barriers )
11) C ++ 11 中 memory_order_consume 的作用
https://preshing.com/20140709/the-purpose-of-memory_order_consume-in-cpp11/
12) MESI (多核 CPU 缓存一致性协议)
https://en.wikipedia.org/wiki/MESI_protocol
13) MESIF (多核 CPU 缓存一致性协议)
https://en.wikipedia.org/wiki/MESIF_protocol
M 修改( Modified ) |
该 Cache line (缓存行)有效,数据被修改( dirty )了,和主存中的数据不一致,数据只存在于本 Cache 中 |
E 独占互斥( Exclusive ) |
该 Cache line 只被缓存在该 CPU 的缓存中,它是未被修改过的( clean ),与主存中数据一致 |
S 共享( Shared ) |
该 Cache line 有效,数据和内存中的数据一致,数据存在于很多 Cache 中 |
I 无效( Invalid ) |
该 Cache line 无效,可能有其它 CPU 修改了该 Cache line |
F 转发( Forward ) |
Intel 提出来的,意思是一个 CPU 修改数据后,直接针修改的结果转发给其它 CPU |
以上所述就是小编给大家介绍的《C和C++中的volatile、内存屏障和CPU缓存一致性协议MESI》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 垃圾回收之写屏障
- CyclicBarrier - 同步屏障实现分析
- Java 并发编程(三):MESI、内存屏障
- Java 并发编程(三):MESI、内存屏障
- 刘睿民:数据库是保障数据安全的有力屏障
- Golang三色标记、混合写屏障GC模式图文全分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。