C/C++教程

c++ memory order

本文主要是介绍c++ memory order,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

https://sf-zhou.github.io/brpc/brpc_01_bthread.html

memory_order_relaxed

不保证同步操作,不会将一定的顺序强加到并发内存访问上,只保证原子性和修改顺序一致性

x = y = 0
// Thread 1:
r1 = y.load(std::memory_order_relaxed); // A
x.store(r1, std::memory_order_relaxed); // B
// Thread 2:
r2 = x.load(std::memory_order_relaxed); // C 
y.store(42, std::memory_order_relaxed); // D

D 能出现在 A前面,B 能出现在 C前面,D也能出现在C前面,因为编译器重排和运行期乱序,最后的结果是可能是 r1 == r2 == 42 的

松弛内存序通常用于计数器的递增,例如对 shared_ptr 引用计数的递增,但是递减不是。

总结:存在一个全序修改序,在同一线程对同一个内存地址的操作是不会重排的。

x.compare_exchange_weak(
						oldVal, // expected
						oldVal+1, // desired
						memory_order_relaxed);

memory_order_release-memory_order_acquire

memory_order_release 的 store 的线程 A,和 memory_order_acquire 的 load 的线程 B。

所有的写内存的操作(非原子的,松弛内存写)都将会会 happened-before 于 store 线程 A,在 A 看来,
所有的写操作都完成了,对于线程B来说,所有的写都将变得可见,也就是说,一旦B完成了 load 操作,线程B能看到所有
线程A写到内存的东西。这个承诺仅在一个 load 线程B返回了 A 所 store 的值,或者在接下来的 release 序列中。

一个线程内任何 happened-before 于 store 的事件,都将在 load 的线程中作为副作用(可见),换句话说,load 的副作用让 store 前的事件变得可见。

同步建立在相同的变量上进行 release 和 acquire,其他未同步的线程将会见到不同的内存序

在强序的系统上,比如在X86,IBM上,release-acquire 是大多数的指令操作是自动会应用的,不需要提供其他的同步指令,在一些弱序架构上,比如ARM和Itanium上就需要在load前加上memory fence,还有会影响一些编译器的优化。
这种序通常会用在mutex和spin lock上,也就是说当修改线程在临界区执行完成后,任何修改对于其他线程都是可见的。

总结:所有在 store-release 所在线程的操作对于 load-acquire 都是可见的。

memory_order_release-memory_order_consume

(相关的细节还在修订,并且暂时不鼓励)

和 memory_order_release-memory_order_acquire 的区别在于,只会在依赖的变量上是可见的。当 memory_order_consume 的 load 完成后,标记 memory_order_release 在原子变量的修改对于所有在 load 之后且在这个变量上的函数和操作都是可见的。

DEC Alpha 的主流 cpu 都是自动支持这种序的。

是 release-acquire 的轻量版本,带入了依赖,consume 依赖某个变量 X,那么在 store-release 的线程中对变量X的操作都是先发于(happend-before) consume

Sequentially-consistent ordering

原子操作默认是seq_consist

    bool
    load(memory_order __m = memory_order_seq_cst) const noexcept
    { return _M_base.load(__m); }

memory_order_seq_cst 不仅仅用 memory_order_release-memory_order_acquire 的顺序排序,还将对带有 memory_order_seq_cst
的原子操作提供一个单独的全序修改顺序 single total modification order

操作B memory_order_seq_cst 的 load 原子变量 M,遵从下面这些条件:

  • 最后一次修改 M 的操作 A 的结果,将会 single total order 的 B前出现。
  • 如果 A 不是 memory_order_seq_cst,则B可能观察到不是

single total order 全序偏序:
偏序指的是只能为系统中的部分事件定义先后顺序。这里的部分其实是有因果关系的事件。
全序是指所有的事件都可以区分先后顺序。无论是真实或是虚拟世界,这都是受欢迎的。因为,所有的事件都有了一个统一的评判标准,我们一直欢迎统一而拒绝分裂。
lamport http://zhangtielei.com/posts/blog-time-clock-ordering.html

Sequential ordering 可能对于多个 producer 和多个 consumer 有效,并且观测到所有 producer 的都保持一个一致的顺序。Total sequential ordering 会要求一个完全的内存栅栏指令到多核系统中,会成为系统的瓶颈。

其他:
https://www.think-cell.com/en/career/talks/pdf/think-cell_talk_memorymodel.pdf

Dekker's algorithm 和 Peterson

用途

正确的线程间同步
实现lock-free算法
![[Pasted image 20220422175119.png]]

cpu cache

cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size

查看Cache Line 的大小,会顺序加载64字节

  • 可以测试false shared

如何知道在内存还是在cache内?内存和cache如何对应
一种是 Direct Mapped Cache 来做,通过对内存取模映射到 cacheline上
多个内存缓存在一起怎么办?cache line中有组标记 tag,区分不同的内存块,和有效位 valid,判断是否需要强制从内存取值。内存地址被分成 组标记 + 索引 + 偏移量 得到在 cache block 的地址。

缓存命中率提高:
unlikely
sched_setaffinity

什么时候才把cache的数据写回内存?

  • 写直达(write through):把数据同时写入内存和 Cache 中,这种方法称为写直达(Write Through,写操作会很花费事件
  • 写回(write back):新数据仅仅被写入到 cache block,只有当 block 需要被替换的时候,才会刷到内存中去。新数据写入到 cache block,当前cache block 会被标记为 dirty,只有写入的地址不是当前cache block所属组标记的时候,才会重刷缓存。如果是dirty,将数据写回到内存,否则直接加载新的 cache block。

缓存一致性

https://zhuanlan.zhihu.com/p/269221065

内存序 相关资料pdf阅读

Memory Models for C/C++ Program:
https://arxiv.org/pdf/1803.04432.pdf

这篇关于c++ memory order的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!