一文带你读懂c++ memory_order

  介绍

一文带你读懂c++ memory_order ?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

看了c++并发编程实战的内存模型部分后,一直对memory_order不太懂,今天在知乎发现了百度的brpc,恰好有关于原子操作的文档,感觉解释的很好。为了加深理解,再次总结一遍。

在多核编程中,我们使用锁来避免多个线程修改同一个数据时产生的竞争条件。但是,锁会消耗系统资源,当锁成为性能瓶颈的时候,就需要使用另一种方法——原子指令C + + 11中引入了原子类型原子。

原子指令(x均为std::原子)作用x.load()返回x的值.x.store (n)把x设为n,什么都不返回.x.exchange (n)把x设为n,返回设定之前的值.x.compare_exchange_strong (expected_ref,所需的)若x等于expected_ref,则设为,返回成功,否则把最新值写入expected_ref,返回失败.x.compare_exchange_weak (expected_ref所需)相比compare_exchange_strong可能有虚假wakeup.x.fetch_add (n), x.fetch_sub (n)原子地做x +=n, x -=n,返回修改之前的值。

但仅靠原子指令实现不了对资源的访问控制。这造成的原因是编译器和cpu实施了重排指令,导致读写顺序会发生变化,只要不存在依赖,代码中后面的指令可能会被放在前面,从而先执行它.cpu这么做是为了尽量塞满每个时钟周期,在单位时间内尽量执行更多的指令,从而提高吞吐率。

下面看个例子:

//线程1//准备初始化错误
  p.init ();
  准备好=true; 
//线程2
  如果(准备){
  p.bar ();
  }

线程2在准备好了为正确的时候会访问p,对线程1来说,如果按照正常的执行顺序,那么p先被初始化,然后在将准备赋为真的。但对多核的机器而言,情况可能有所变化:

    <李>线程1中的准备=true可能会被cpu或编译器重排到p.init()的前面,从而优先执行准备=true这条指令。在线程2中,p.bar()中的一些代码可能被重排到如果(准备)之前。 <李>即使没有重排,准备和p的值也会独立地同步到线程2所在核心的缓存,线程2仍然可能在看到准备为真时看到未初始化的p。

为了解决这个问题,cpu和编译器提供了记忆,让用户可以声明访存指令的可见性关系,c++ 11总结为以下记忆顺序:

记忆顺序作用memory_order_relaxed无击剑作用,cpu和编译器可以重排指令memory_order_consume后面依赖此原子变量的访存指令勿重排至此条指令之前memory_order_acquire后面访存指令勿重排至此条指令之前memory_order_release前面的访存指令勿排到此条指令之后。当此条指令的结果被同步到其他核的缓存中时,保证前面的指令也已经被同步.memory_order_acq_relacquare + releasememory_order_seq_cstacq_rel +所有使用seq_cst的指令有严格的全序关系

有了memoryorder,我们可以这么改上面的例子:

//Thread1//准备初始化错误
  p.init ();
  准备好了。存储(的确,std:: memory_order_release); 
//Thread2
  如果(ready.load (std:: memory_order_acquire)) {
  p.bar ();
  }

线程2中的收购和线程1的释放配对,确保线程2在看到准备==true时能看到线程1释放之前所有的访存操作。

注意,记忆栅栏不等于可见性,即使线程2恰好在线程1在把准备设置为真正的后读取了准备也不意味着它能看到真的,因为同步缓存是有延时的.memory栅栏保证的是可见性的顺序:“假如我看到了一个的最新值,那么我一定也得看到b的最新值”。

关于一文带你读懂c++ memory_order问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注行业资讯频道了解更多相关知识。

一文带你读懂c++ memory_order