获取负载是否可以与其他获取操作一起重新排序? cppreference 表示只有非原子的和松弛的是通过获取排序的
Can acquire loads reorder with other acquire operations? cppreference says only non-atomic and relaxed are ordered by acquire
根据C++ Reference,mutex.lock()
是一个memory_order_acquire
操作,mutex.unlock()
是一个memory_order_release
操作。
但是,memory_order_acquire
和memory_order_release
只对非原子和宽松的原子操作有效。
memory_order: Release-Acquire ordering on cppreference
If an atomic store in thread A is tagged memory_order_release
and an atomic load in thread B from the same variable is tagged memory_order_acquire
, all memory writes (non-atomic and relaxed atomic) that happened-before the atomic store from the point of view of thread A, become visible side-effects in thread B
C++中的mutex能否保证原子操作的可见性?一个例子如下。代码 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;
}
TL:DR:“所有内存写入”的意思是全部,而不是只是提到的种类,但措辞令人困惑。可能只是想指出 even non-atomic 和宽松的原子操作在 synchronizes-with 中是安全可见的,但措辞中缺少“包括”一词。
请注意,cppreference 是一个旨在解释 标准的 wiki。 它不是规范的技术语言,有时甚至会解释一些东西与 ISO C++ 标准不同的术语。
它通常非常 好,但不要在某些事情看起来很奇怪时就认为它是完美的。从周围的上下文(和理智)来看,就像段落中的最后一句话没有任何限制地说“一切”,这仍然是相当明显的意思。
ISO C++ 很清楚。 “看到”释放操作的获取操作创建 synchronizes-with 关系。 发布前的所有内容在获取操作后对代码可见。
因此,就访问全局一致共享内存状态的操作的模型而言,获取操作会阻止 所有内容 在它们之前重新排序。包括发布和 seq_cst 操作。 (请注意,cppreference 的这一部分没有提及 reordering,只是为了保证可见性或不可见性。访问全局一致状态的本地重新排序实际上是真实 CPU 的工作方式,所以这样描述事情通常更方便,就像你在问题中所做的那样。)
这意味着 C++ 的获取和释放定义符合标准术语,没有疯狂的魔法异常。 https://preshing.com/20120913/acquire-and-release-semantics/
请注意,有些人使用“松散原子”来描述所有弱于 seq_cst
的顺序。示例:Herb Sutter 在 this question 的演讲中就是这样使用它的。
这可能就是 cppreference 定义中的意思,但我不知道他们为什么要排除 seq_cst
。所有原子操作和 non-atomic 操作都是有序的。所以也许他们的意思是mo_relaxed
,只是想指出甚至那些是有序的/可见的。
(seq_cst
可以说已经订购了 本身 wrt. everything else, 所以“当然”订购了关于获取和释放操作。但这个原因似乎不太可能。)
如果是为了强调它也订购了较弱的订单,他们应该写“including non-atomic and放松的原子。如果没有“包括”一词,该措辞可以理解为暗示 仅 non-atomic 和 relaxed-atomic。只有了解大局,什么是理智,什么是理智,才能给你正确的阅读。
需要准确理解的技术写作,经常会用到“包括但不限于”这句话。
另请注意,您的示例仍然可以触发断言,只是不是因为您担心的原因。
如果线程 a
启动缓慢,线程 b
可以先进入其临界区并在另一个线程中的打印+存储发生之前打印+读取 x
。
编写这样的玩具示例的通常方法是一个循环,该循环在获取负载上旋转,直到它看到一个值,例如像 data_read
这样的标志由释放操作存储在您关心的存储之后。这样你就知道读取端在获取操作之后运行,synced-with 写入端的释放操作。
根据C++ Reference,mutex.lock()
是一个memory_order_acquire
操作,mutex.unlock()
是一个memory_order_release
操作。
但是,memory_order_acquire
和memory_order_release
只对非原子和宽松的原子操作有效。
memory_order: Release-Acquire ordering on cppreference
If an atomic store in thread A is tagged
memory_order_release
and an atomic load in thread B from the same variable is taggedmemory_order_acquire
, all memory writes (non-atomic and relaxed atomic) that happened-before the atomic store from the point of view of thread A, become visible side-effects in thread B
C++中的mutex能否保证原子操作的可见性?一个例子如下。代码 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;
}
TL:DR:“所有内存写入”的意思是全部,而不是只是提到的种类,但措辞令人困惑。可能只是想指出 even non-atomic 和宽松的原子操作在 synchronizes-with 中是安全可见的,但措辞中缺少“包括”一词。
请注意,cppreference 是一个旨在解释 标准的 wiki。 它不是规范的技术语言,有时甚至会解释一些东西与 ISO C++ 标准不同的术语。
它通常非常 好,但不要在某些事情看起来很奇怪时就认为它是完美的。从周围的上下文(和理智)来看,就像段落中的最后一句话没有任何限制地说“一切”,这仍然是相当明显的意思。
ISO C++ 很清楚。 “看到”释放操作的获取操作创建 synchronizes-with 关系。 发布前的所有内容在获取操作后对代码可见。
因此,就访问全局一致共享内存状态的操作的模型而言,获取操作会阻止 所有内容 在它们之前重新排序。包括发布和 seq_cst 操作。 (请注意,cppreference 的这一部分没有提及 reordering,只是为了保证可见性或不可见性。访问全局一致状态的本地重新排序实际上是真实 CPU 的工作方式,所以这样描述事情通常更方便,就像你在问题中所做的那样。)
这意味着 C++ 的获取和释放定义符合标准术语,没有疯狂的魔法异常。 https://preshing.com/20120913/acquire-and-release-semantics/
请注意,有些人使用“松散原子”来描述所有弱于 seq_cst
的顺序。示例:Herb Sutter 在 this question 的演讲中就是这样使用它的。
这可能就是 cppreference 定义中的意思,但我不知道他们为什么要排除 seq_cst
。所有原子操作和 non-atomic 操作都是有序的。所以也许他们的意思是mo_relaxed
,只是想指出甚至那些是有序的/可见的。
(seq_cst
可以说已经订购了 本身 wrt. everything else, 所以“当然”订购了关于获取和释放操作。但这个原因似乎不太可能。)
如果是为了强调它也订购了较弱的订单,他们应该写“including non-atomic and放松的原子。如果没有“包括”一词,该措辞可以理解为暗示 仅 non-atomic 和 relaxed-atomic。只有了解大局,什么是理智,什么是理智,才能给你正确的阅读。
需要准确理解的技术写作,经常会用到“包括但不限于”这句话。
另请注意,您的示例仍然可以触发断言,只是不是因为您担心的原因。
如果线程 a
启动缓慢,线程 b
可以先进入其临界区并在另一个线程中的打印+存储发生之前打印+读取 x
。
编写这样的玩具示例的通常方法是一个循环,该循环在获取负载上旋转,直到它看到一个值,例如像 data_read
这样的标志由释放操作存储在您关心的存储之后。这样你就知道读取端在获取操作之后运行,synced-with 写入端的释放操作。