std::mutex 的发布-获取可见性保证是否仅适用于关键部分?
Do the release-acquire visibility guarantees of std::mutex apply to only the critical section?
我试图理解标题下的这些部分 发布-获取排序https://en.cppreference.com/w/cpp/atomic/memory_order
他们说关于原子负载和存储:
如果线程 A 中的原子存储被标记为 memory_order_release 和 标记了来自同一变量的线程 B 中的原子负载 memory_order_acquire,所有内存写入(非原子和放松) 原子)发生-从原子存储之前从角度来看 线程 A,在线程 B 中成为可见的副作用。也就是说,一次 原子加载完成,线程B保证看到一切 线程 A 写入内存。
然后关于互斥体:
互斥锁,如 std::mutex 或原子自旋锁,是一个 释放-获取同步的示例:释放锁时 通过线程 A 和线程 B 获取,发生的一切 线程 A 上下文中的关键部分(发布之前) 必须对正在执行的线程 B(获取后)可见 相同的关键部分。
第一段似乎说,一个原子加载和存储(用memory_order_release
,memory_order_acquire
)线程B保证看到线程A写入的所有内容。 包括非原子写入。
第二段似乎表明互斥锁的工作方式相同,只是B 可见的范围仅限于关键部分中包装的任何内容,这是一个准确的解释吗? 还是每次写入,即使是关键部分之前的那些,B也可以看到?
我认为关于互斥体的 cppreference 引用之所以以这种方式编写,是因为如果您使用互斥锁进行同步,则所有用于通信的共享变量都应始终在关键部分内访问。
2017年标准在4.7.1中说:
获取互斥锁的调用将在 包含互斥锁的位置。相应地,一个释放的呼叫 相同的互斥锁将对这些互斥锁执行释放操作 地点。非正式地,对 A 力执行释放操作 先前对其他内存位置的副作用变得可见 稍后在其上执行消耗或获取操作的其他线程 一个。
更新:我想确保我有一个可靠的帖子,因为在网络上很难找到这些信息。感谢@Davis Herring为我指出正确的方向。
标准说
在33.4.3.2.11和33.4.3.2.25中:
互斥锁解锁与后续锁定操作同步,这些操作获得 同一对象的所有权
(https://en.cppreference.com/w/cpp/thread/mutex/lock、https://en.cppreference.com/w/cpp/thread/mutex/unlock)
在4.6.16中:
与全表达式相关的每个值计算和副作用在与要评估的下一个全表达式相关的每个值计算和副作用之前进行排序。
https://en.cppreference.com/w/cpp/language/eval_order
在4.7.1.9中:
一个评估 A线程间发生在评估 B 之前,如果
4.7.1.9.1) -- A 与 B 同步,或
4.7.1.9.2) -- A 在 B 之前按依赖顺序排序,或
4.7.1.9.3) -- 用于某些评估 X
4.7.1.9.3.1) ------ A 与 X 同步,X 在 B 之前排序,或
4.7.1.9.3.2) ------ A 在 X 和 X 线程间在 B 之前发生排序,或
4.7.1.9.3.3) ------ 线程间发生在 X 之前,X 线程间发生在 B 之前。
https://en.cppreference.com/w/cpp/atomic/memory_order
- 因此,在 4.7.1.9.1 之前,互斥解锁 B 线程发生在后续锁 C之前。
- 在互斥锁解锁 B 之前按程序顺序发生的任何评估 A 也会在 C之前发生4.7.1.9.3.2
- 因此,在
unlock()
保证所有以前的写入,即使是关键部分之外的写入,都必须对匹配的lock()
可见。
这个结论与今天(和过去)实现互斥锁的方式一致,因为所有程序顺序的先前加载和存储都是在解锁之前完成的。(更准确地说,当任何线程中的匹配锁定操作观察到解锁时,存储必须可见。毫无疑问,这是理论上和实践中公认的释放定义。
这里没有魔法:互斥锁部分只是描述常见情况,其中(因为每次访问关键部分都可能写入共享数据)有问题的编写者使用互斥锁保护其所有访问。 (其他较早的写入是可见的,并且可能是相关的:考虑在不同步的情况下创建和初始化对象,然后将其地址存储在关键部分的共享变量中。
第一段似乎说原子加载和存储(与 memory_order_release,memory_order_acquire)线程B保证 查看线程 A 编写的所有内容。包括非原子写入。
不仅仅是写入,所有内存操作都已完成;您可以看到读取也已完成:虽然读取当然不会产生副作用,但您可以看到在发布之前读取永远不会看到在获取之后写入的值。
https://en.cppreference.com/都坚持写入(易于解释),完全忽略了正在完成读取的问题。
第二段似乎暗示互斥锁的工作方式相同, 除了对 B 可见的范围仅限于任何 包裹在关键部分,这是一个准确的解释吗? 或者每次写作,即使是在关键部分之前的那些 对 B 可见?
但"在关键部分"甚至不是一回事。您执行的任何操作都无法与完成操作的内存状态分开。当您在"关键部分"设置整数对象时,该对象必须存在;将"写入对象"视为隔离是没有意义的,因为没有对象可谈。严格解释,"关键部分"将仅涵盖在其中创建的对象。但是这些对象都不会被其他线程知道,因此没有什么需要保护的。
因此,"关键部分"的结果本质上是程序的整个历史记录,对共享对象的某些访问仅在互斥锁之后开始。
- 确定夏令时是否适用于特定日期
- 是否有一种 STL 算法可以最后找到,但它也适用于指针?
- C++17 和更新的 std::分配器是否适用于动态数量的自定义堆?
- NRVO 是否也适用于协程?
- 约束包容是否仅适用于概念?
- std::mutex 的发布-获取可见性保证是否仅适用于关键部分?
- 使用迭代器成员函数是否仅适用于某些向量类型"empty()"?
- 别名漏洞是否适用于签名字符?
- 指针算法是否适用于迭代器?
- 保证复制 elis 是否适用于函数参数?
- 尾部调用优化是否适用于此功能?
- 值初始化是否适用于原子对象?
- 是否有适用于迭代器的数字解析函数
- 严格别名规则是否适用于跨函数调用
- Scott Meyers关于首选非成员非友元方法的建议是否适用于对象构造?
- 对函数参数的要求是否也适用于初始值设定项列表?
- 在这种情况下,(N)RVO是否适用于我的功能
- 是否有适用于张量流对象检测 API 的C++包装器
- GPU cuda 代码是否适用于多个 GPU 卡而无需任何实现
- std::copy_n 是否适用于重叠范围?