C++ 是否可以使用其他获取操作获取负载重新排序?cppreference说只有非原子的和松弛的才由acquire排序

C++ 是否可以使用其他获取操作获取负载重新排序?cppreference说只有非原子的和松弛的才由acquire排序,c++,mutex,atomic,memory-model,stdatomic,C++,Mutex,Atomic,Memory Model,Stdatomic,根据mutex.lock()是一种内存顺序(memory\u order\u acquire)操作mutex.unlock()是一种内存顺序(memory\u order\u release)操作 然而,memory\u order\u acquire和memory\u order\u release仅对非原子操作和放松原子操作有效 论cpp参考 如果线程A中的原子存储被标记为memory\u order\u release,线程B中来自同一变量的原子负载被标记为memory\u order\u

根据mutex.lock()是一种
内存顺序(memory\u order\u acquire)
操作mutex.unlock()是一种
内存顺序(memory\u order\u release)
操作

然而,
memory\u order\u acquire
memory\u order\u release
仅对非原子操作和放松原子操作有效

论cpp参考

如果线程A中的原子存储被标记为
memory\u order\u release
,线程B中来自同一变量的原子负载被标记为
memory\u order\u acquire
,那么从线程A的角度来看,在原子存储之前发生的所有内存写入(非原子和松弛原子)在线程B中都会产生明显的副作用

C++中互斥可以保证原子操作的可见性吗?下面是一个例子。代码

A
是否可以在
mu.lock()
之前重新排序,线程
b
是否可以将
x
读取为
false


#include <thread>
#include <atomic>
#include <cassert>
#include <iostream>
#include <unistd.h>

std::atomic<bool> x = {false};
std::mutex mu;

void write_x(){
  mu.lock();
  std::cout << "write_x" << std::endl;
  x.store(true, std::memory_order_release);
  mu.unlock();
}

void read_x() {
  mu.lock();
  std::cout << "read_x" << std::endl;
  assert(x.load(std::memory_order_acquire)); // A
  mu.unlock();
}

int main() {
  std::thread a(write_x);
  usleep(1);
  std::thread b(read_x);

  a.join(); b.join();

  return 0;
}


#包括
#包括
#包括
#包括
#包括
std::原子x={false};
std::互斥mu;
无效写入_x(){
mu.lock();
std::coutTL:DR:“所有内存写入”的意思是所有,不仅仅是提到的种类,而且措辞令人困惑。可能只是想指出,即使是非原子的和放松的原子操作也可以安全地在与的同步中看到,但措辞缺少“包括”一词


<强>注意CPPYPE是一种维基解释标准。它不是规范的技术语言,有时甚至不同于ISO C++标准解释不同的事物。

一般来说,这是非常好的,但不要只是假设它在某些事情看起来很奇怪时是完美的。从周围的上下文(和理智)来看,就像这一段的最后一句说“一切”,没有任何限制,这仍然是相当明显的意思


ISO C++是明确的,一个获取操作“看到”释放操作创建一个与关系同步。释放之前的所有东西在获取操作之后都是可见的代码。 因此,在一个模型中,访问全局一致共享内存状态的操作,acquire操作阻止了所有在它们之前重新排序的操作,包括release和seq_cst操作。(请注意,CPPPreference的这一部分并没有提到重新排序,只是为了保证可见性。访问全局相干态的局部重新排序实际上是真实CPU的工作方式,因此通常以这种方式描述事情更方便,就像您在问题中所做的那样。)

这意味着C++对acquire和release的定义与标准术语相匹配,没有异常


注意,有些人用“放松原子学”来描述所有弱于
seq_cst
的排序。例如:Herb Sutter在讨论中就是这样使用的

这可能是cppreference定义中的意思,但IDK解释了为什么他们要排除
seq_cst
。所有原子和非原子操作都是有序的。因此,他们的意思可能是
mo_relaxed
,只是想指出,即使这些操作也是有序的/可见的

seq_cst
可以说已经订购了自己的所有东西,所以“当然”订购的是获取和发布操作。但这一原因似乎不太可能。)


如果它是为了强调弱序也是由它排序的事实,他们应该写“包括非原子的和松弛的原子的”,而不写“包括”,这句话可以理解为只意味着非原子的和放松的原子的。只有对大局的理解,以及什么是理智的或不理智的,才能给你一个正确的解读

需要准确理解的技术写作通常会使用“包括但不限于”这一短语


还请注意,您的示例仍然可以触发断言,只是不是出于您担心的原因

如果线程
a
启动缓慢,则线程
b
可以首先进入其关键部分并在另一个线程中发生打印+存储之前打印+读取
x

编写这样的玩具示例的常用方法是循环,循环在获取负载上旋转,直到它看到一个值,例如,在您关心的存储之后由释放操作存储的一个标志,如
data\u read
。这样您就知道读取端在与写入端的释放操作同步的获取操作之后运行。

TL:DR:“所有内存写入”意味着所有,不仅仅是提到的种类,而且措辞令人困惑。可能只是想指出,即使是非原子的和放松的原子操作也可以安全地在同步中看到,但措辞缺少“包括”一词


<强>注意CPPYPE是一种维基解释标准。它不是规范的技术语言,有时甚至不同于ISO C++标准解释不同的事物。

一般来说,这是非常好的,但不要只是假设它在某些事情看起来很奇怪时是完美的。从周围的上下文(和理智)来看,就像这一段的最后一句说“一切”,没有任何限制,这仍然是相当明显的意思


ISO C++是明确的,一个获取操作“看到”释放操作创建一个与关系同步。释放之前的所有东西在获取操作之后都是可见的代码。 因此,在一个模型中,访问全局一致共享内存状态的操作,acquire操作会阻止所有在它们之前重新排序的操作,包括release和seq_cst操作。(注意,CPPPreference的这一部分并没有提到重新排序。)