cpp内存顺序(一)

理解了 c++ concurrency in action 5.3.4 小节的内容:Release sequences and synchronizes-with后,对C++ consistency model本质的理解非常有好处.

另外读过lamport 的经典论文Time, Clocks, and the Ordering of Events in a Distributed System对理解 C++ memory order 也很有好处.

JAVA 与C++ 的内存模型同根同源,都出自与DRF0.

以下是我的理解,有错误地方,请指出,谢谢!

从cpu 角度讲:

  1. 每个 cpu 按照自己的 clock 在自己世界里独立乱序执行.
  2. 当某个cpu0对某个原子变量X进行了X.store(a, …)操作,且memory_order的参数不管是什么;然后cpu1 通过X.load(…), 且memory_order 参数不管是什么,如果(不一定立即)cpu1看到了cpu0对X的修改结果a(这期间没有其他cpu修改X), 那么我们一定可以确认 X.store(a) 这个事件一定是发生在X.load() 事件之前(好像是废话,但很重要)。这就好比 lamport 论文中 cpu0 给cpu1 发送了一个消息 ,cpu1 接受了这个消息, 那这个 发送消息事件一定是在接收消息事件之前发生.
  3. 总结上面两点:每个cpu的运行是各自独立的,一旦两个cpu之间通过对共享原子变量进行操作(至少有一个为store or RMW, 但不能两个都为store)就好比是 一个cpu 发送了消息,一个接受到了这条消息,此时,发送消息这个事件与接受消息这个事件就产生了先后关系,我们可以称为一个同步点(synchronizes point)

从Lock-free 程序设计角度:

  1. 从Lock-free程序设计角度,我们就是需要借助这个synchronizes point,来同步更多的事件来达到我们想要的memory consistency model.比如C++ Release-Acquire 语义, 我们通过在某个synchronizes point的两端施加相对应的memory barrier(内存栅栏或者内存屏障)这样我们就获得了更多的同步关系,即C++ memory consistency model 的Acquire-Release 语义.

注意:一定要在synchronizes point的两端都要加memory barrier .

memory barrier 从程序员的角度看,是对单个cpu内部 执行内存操作指令顺序的一种局部 限制,而这个synchronizes point 建立了 cpu 之间的一种运行时 先后关系,这样我们就可以获得CPU 之间的更多的先后关系.

  1. 另外上面的synchronizes point 是在运行期建立的, 即:cpu1在运行过程中确实看到了cpu0对同一原子变量写入的那个值, 则cpu1 与cpu0 在这个时刻的synchronizes point 建立。

另外下面是,我们在设计Lock-Free 程序时,需要注意的:

  1. C++ memory consistency model 标准从语义上讲定义的是原子变量操作之间内存可见性的顺序, 并没有保证单个原子变量操作的内存立即可见性.

    1. 即使是最严格sequentially consistency memory model 的 x.store(a, seq_cst), x.load(seq_cst) 操作, 我们也不能假设x.load(seq_cst) 一定能读到最新值.虽然在具体实现层面(不同平台),会读到x的最新值.
    2. 如果在特殊应用场景,我们需要保证原子变量立即可见性,则可以根据具体的平台实现,来调用对应的cpu指令。比如在X86 平台上mfence指令;比如BRPC 中对 worksteal queue 的实现.
  2. 原子变量RMW操作的立即可见性性质。如果cpu0 对x 进行了修改操作(包括RMW), cpu1立即读x(包括RWM),cpu1都不一定能拿到最新值,以下情况除外:cpu0 与 cpu1 对x的操作都是是RMW 操作(比如CAS, fetch_add ), 指定的memory order 任意都可以.

  3. synchronizes-with relationship 专指线程之间的关系.happens-before 包括线程内部.

  4. 原子变量 + memory barrier 实现了memory consistency model语义, 而memory consistency model 语义是 一切上层建筑(锁等)的基础, 比如:unlock-lock 就是满足Release-Acquire 语义,所以在临界区内修改数据就是安全的,且这些数据可以为非原子操作, 因为两个线程不会同时进入临界区,他们好像被限制了执行顺序一样.

  5. modification order 的定义:

参考 https://en.cppreference.com/w/cpp/atomic/memory_order

  1. Release sequence 与synchronizes-with : 参考c++ concurrency in action 5.3.4
当涉及到内存顺序时,最常见的测试代码是使用原子操作和多线程来模拟并发访问共享资源的情况。下面是一个简单的示例代码,展示了使用C++中的atomic和thread库来测试内存顺序的效果: ```cpp #include <iostream> #include <atomic> #include <thread> #include <vector> std::atomic<int> counter(0); // 共享计数器 void increment() { for (int i = 0; i < 1000; ++i) { counter.fetch_add(1, std::memory_order_relaxed); // relaxed内存顺序 } } int main() { std::vector<std::thread> threads; for (int i = 0; i < 10; ++i) { threads.emplace_back(increment); } for (auto& thread : threads) { thread.join(); // 等待所有线程执行完成 } std::cout << "Counter value: " << counter << std::endl; return 0; } ``` 在上述代码中,我们使用std::atomic<int>类型的counter作为共享资源,并在多个线程中对其进行递增操作。每个线程通过调用counter.fetch_add()函数来递增计数器的值,使用std::memory_order_relaxed指定了放松顺序内存顺序模型。 运行代码后,会创建10个线程并发执行递增操作,最终输出计数器的值。由于使用了放松顺序内存模型,线程之间的操作可能会被重排,因此计数器的最终值可能不是精确的10000。如果想要更严格的内存顺序保证,可以修改为其他内存顺序模型,如std::memory_order_seq_cst。 需要注意的是,代码中使用了C++11引入的原子操作和内存顺序模型,确保了对共享资源的并发访问是线程安全的。在实际开发中,需要根据具体需求选择合适的内存顺序模型来保证数据的正确性和一致性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值