[c/c++] memory ordering

参考:

std::memory_order - cppreference.com

前言:

memory ordering 又叫内存序,这个翻译其实不直观,更加具体应该叫做 cpu 访问内存的顺序(FIX Me If wrong)。这个概念的引入是为了解决 “多线程读写多变量” 场景下的乱序问题。如果针对多线程读写多变量时使用粒度较粗的锁,那么在不考虑效率的前提下,可以不用考虑memory ordering问题,比如下面的代码已经通过加锁解决了同步问题:

thread 1 :

func(){

lock;

A = 1;
B = 100;
C = 1000;

unlock;

}


thread 2 :

func(){

lock;

A = 9;
b = 99;
c = 999;

unclock;

}

上面的问题是锁的粒度较大,如果想要减小锁的粒度,或者直接不加锁还能保证数据安全,那么就需要 引入 memory ordering 来确保数据安全。

在 c++ 11 中引入了 std::memory_order 来配合 std::atomic 来确保 “多线程读写多变量” 场景下的数据安全和小粒度锁(甚至无锁)的可能性。

注:如果想开启 memory ordering 功能,那么必须使用 std::aotmic 的 load store 对数据读写,不要用 赋值运算符 进行读写。建议在使用 std::atomic 的时候都不要使用 赋值运算符,能用 load 和 store 尽量用。

小粒度锁且存在乱序隐患的例子:

// 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

上面的代码段中,有以下几点(fix me if wrong):

1)Thread 1 和 Thread 2 的执行的先后顺序无法保证;

2)Thread 1中的 A 和 B 谁先执行可以保证(因为两条语句有因果关系),同样 Thread 2 中的 C 和 D 谁先执行无法保证(因为两条语句没有因果关系)。

3)对与2),在ARM 这种 weak-ordering 的架构上来说,极可能出现 D 先于 C 执行,因为 C 和 D 的赋值没有因果关系,编译器可能会在代码优化时把 D 放到 C之前,也有可能 CPU 在执行时,发现如果先执行 D 后执行 C 会快很多,所以会先执行D。

memory ordering 引入的是为了解决编译器优化导致的乱序和cpu的乱序执行问题。

回到上面的程序,如果执行顺序是 D-A-B-C , 那么 r1 和 r2 都会得到值 42 。

如果把上面语句改成:

// Thread 1:
lock;
r1 = y.load(std::memory_order_relaxed); // A
x.store(r1, std::memory_order_relaxed); // B
unlock;

// Thread 2:
lock;
r2 = x.load(std::memory_order_relaxed); // C 
y.store(42, std::memory_order_relaxed); // D
unlock;

那么就永远不会出现 D-A-B-C 的问题,因为 CD 和 AB 永远要放到一起执行,这便是前言里说的大粒度锁不需要考虑乱序(但是还是无法那个线程先执行)。

正文:

了解了 memory ordering 为什么出现以后,下面介绍如何使用。

c++ 11 提供了如下几种 order :

Defined in header <atomic>

ValueExplanation
memory_order_relaxedRelaxed operation: there are no synchronization or ordering constraints imposed on other reads or writes, only this operation's atomicity is guaranteed (see Relaxed ordering below)
memory_order_consumeA load operation with this memory order performs a consume operation on the affected memory location: no reads or writes in the current thread dependent on the value currently loaded can be reordered before this load. Writes to data-dependent variables in other threads that release the same atomic variable are visible in the current thread. On most platforms, this affects compiler optimizations only (see Release-Consume ordering below)
memory_order_acquireA load operation with this memory order performs the acquire operation on the affected memory location: no reads or writes in the current thread can be reordered before this load. All writes in other threads that release the same atomic variable are visible in the current thread (see Release-Acquire ordering below)
memory_order_releaseA store operation with this memory order performs the release operation: no reads or writes in the current thread can be reordered after this store. All writes in the current thread are visible in other threads that acquire the same atomic variable (see Release-Acquire ordering below) and writes that carry a dependency into the atomic variable become visible in other threads that consume the same atomic (see Release-Consume ordering below).
memory_order_acq_relA read-modify-write operation with this memory order is both an acquire operation and a release operation. No memory reads or writes in the current thread can be reordered before the load, nor after the store. All writes in other threads that release the same atomic variable are visible before the modification and the modification is visible in other threads that acquire the same atomic variable.
memory_order_seq_cstA load operation with this memory order performs an acquire operation, a store performs a release operation, and read-modify-write performs both an acquire operation and a release operation, plus a single total order exists in which all threads observe all modifications in the same order (see Sequentially-consistent ordering below)

详细介绍可以见相应文档:

std::memory_order - cppreference.com

这里做个简单的小结:

1)在使用std::atomic的时候,如果仅仅想使用atomic 功能,那么在 load 和 store 的时候,请使用 memory_order_relaxed ,这个 order 仅保证 atomic 功能,不会保证 memory order。这也是我们使用 std::aotmic 的时候最想要的功能。

2)如果多线程读写多变量,那么在写线程中请使用 memory_order_release 完成 store 操作,在 读线程中请使用 memory_order_consume 完成 load 操作,这两个标志会保证 atomic 功能的基础上,保证读线程一定会获取到写线程完成写操作之后的值,也就是说读线程的 load 操作不会先于写线程的 store 之前执行。(fix me if wrong)

3)memory_order_acquire 同样是 和 memory_order_release 一并使用,具体待续...

4)待续...

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值