【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
https://zh.cppreference.com/w/cpp/atomic/memory_order
std::atmoic和std::memory_order只有在多cpu多线程情况下,无锁编程才会用到
编译器优化而产生的指令乱序,cpu指令流水线也会产生指令乱序。当然这些乱序指令都是为了同一个目的,优化执行效率。
happens-before:按照程序的代码序执行,对同一线程,就是一个操作要在另一个操作之前执行。对多个线程,某一个线程的操作A要在另一线程的操作B之前发生。若顺序变了,可能就不能达到我们希望的效果。
synchronized-with:不同线程间,对于同一个原子操作,需要同步关系,store()操作一定要先于 load(),也就是说 对于一个原子变量x,先写x,然后读x是一个同步的操作,读x并不会读取之前的值,而是当前写x的值。
int a=0,int b=1;
void func(){
a=b+22;
b=22;
}
func代码没有被编译器优化,按照正常指令执行:
movl b(%rip), %eax ; 将 b 读入 %eax(通用寄存器)
addl $22, %eax ; %eax 加 22, 即 b + 22
movl %eax, a(%rip) ; % 将 %eax 写回至 a, 即 a = b + 22
movl $22, b(%rip) ; 设置 b = 22
优化后:
movl b(%rip), %eax ; 将 b 读入 %eax
movl $22, b(%rip) ; b = 22
addl $22, %eax ; %eax 加 22
movl %eax, a(%rip) ; 将 b + 22 的值写入 a,即 a = b + 2
6种memory_order 主要分成3类,relaxed(松弛的内存序),sequential_consistency(内存一致序),acquire-release(获取-释放一致性)
relaxed(松弛的内存序):没有顺序一致性的要求,也就是说同一个线程的原子操作还是按照happens-before关系,但不同线程间的执行关系是任意。
sequential_consistency(内存一致序):这个是以牺牲优化效率,来保证指令的顺序一致执行,相当于不打开编译器优化指令,按照正常的指令序执行(happens-before),多线程各原子操作也会Synchronized-with,(譬如atomic::load()需要等待atomic::store()写下元素才能读取,同步过程),当然这里还必须得保证一致性,读操作需要在“一个写操作对所有处理器可见”的时候才能读,适用于基于缓存的体系结构。
acquire-release(获取-释放一致性):这个是对relaxed的加强,relax序由于无法限制多线程间的排序,所以引入synchronized-with,但并不一定意味着,统一的操作顺序。
内存屏障Memory Barrier
程序在运行时内存实际的访问顺序和程序代码编写的访问顺序不一定一致,这就是内存乱序访问。内存乱序访问行为出现的理由是为了提升程序运行时的性能。内存乱序访问主要发生在两个阶段:
- 编译时,编译器优化导致内存乱序访问(指令重排)
- 运行时,多 CPU 间交互引起内存乱序访问
Memory Barrier 能够让 CPU 或编译器在内存访问上有序。一个 Memory Barrier 之前的内存访问操作必定先于其之后的完成。Memory Barrier 包括两类:
- 编译器Memory Barrier
- CPU Memory Barrier
内存屏障阻碍了CPU采用优化技术来降低内存操作延迟,必须考虑因此带来的性能损失.
volatile /ˈvɒlətaɪl/
用来修饰变量,通常用于建立语言级别的 memory barrier。
易变性:在汇编层面反映出来,就是两条语句,下一条语句不会直接使用上一条语句对应的volatile变量的寄存器内容,而是重新从内存中读取。
不可优化性:volatile告诉编译器,不要对我这个变量进行各种激进的优化,甚至将变量直接消除,保证程序员写在代码中的指令,一定会被执行。
顺序性:多线程中,C/C++ Volatile变量与非Volatile变量之间的操作,是可能被编译器交换顺序的。
来源:oschina
链接:https://my.oschina.net/u/347414/blog/3118206