获取负载可以使用其他获取操作重新排序吗? CPP首选项表示只有非原子和松弛是按获取排序的



根据C++参考,mutex.lock()是一个memory_order_acquire操作,mutex.unlock()是一个memory_order_release操作。

但是,memory_order_acquirememory_order_release仅对非原子和松弛的原子操作有效。

memory_order:根据 cpp 首选项发布-获取排序

如果线程 A 中的原子存储被标记为memory_order_release并且线程 B 中来自同一变量的原子负载被标记为memory_order_acquire则从线程 A 的角度来看,在原子存储之前发生的所有内存写入(非原子和松散原子)都会成为线程 B 中的可见副作用

C++中的互斥体能否保证原子操作的可见性?示例如下。代码A可以在mu.lock()之前重新排序,线程b读取xfalse吗?


#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;
}

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


请注意,cppreference是一个旨在解释标准的wiki。它不是规范性技术语言,有时甚至用与ISO C++标准不同的术语来解释事物。

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


ISO C++很清楚。 "看到"发布操作的获取操作会创建同步关系。发布之前的所有内容在获取操作后对代码可见。

因此,就访问全局一致的共享内存状态的操作而言,获取操作会阻止所有内容在它们之前重新排序。 包括发布和seq_cst操作。 (请注意,cpp首选项的这一部分没有提及重新排序,只是为了保证可见性与否。 对全局一致性状态的访问进行本地重新排序实际上是真实 CPU 的工作方式,因此以这种方式描述事物通常更方便,就像您在问题中所做的那样。

这意味着C++对获取和释放的定义符合标准术语,没有疯狂的魔法例外。 https://preshing.com/20120913/acquire-and-release-semantics/


请注意,有些人使用"松弛的原子学"来描述所有弱于seq_cst的排序。 示例:赫伯·萨特(Herb Sutter)在这个问题的演讲中以这种方式使用它。

这可能是该 cpp首选项定义中的含义,但 IDK 为什么他们想要排除seq_cst. 所有原子和非原子操作都是有序的。 所以也许他们的意思是mo_relaxed,只是想指出即使是那些也是有序的/可见的。

(seq_cst可以说已经订购了其他一切,所以"当然"它是关于获取和发布操作的。 但这个原因似乎不太可能。


如果它的目的是强调弱顺序也由它排序的事实,他们应该写成">包括非原子和松弛原子"。如果没有"包括"这个词,这个措辞可以被理解为意味着非原子和松弛的原子。 只有了解大局以及什么是理智的,才能给你一个正确的解读。

需要精确理解的技术写作通常会使用短语"包括但不限于"。


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

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

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

最新更新