简体   繁体   English

std::mutex 的发布-获取可见性保证是否仅适用于关键部分?

[英]Do the release-acquire visibility guarantees of std::mutex apply to only the critical section?

I'm trying to understand these sections under the heading Release-Acquire ordering https://en.cppreference.com/w/cpp/atomic/memory_order我试图理解标题下的这些部分Release-Acquire ordering https://en.cppreference.com/w/cpp/atomic/memory_order

They say regarding atomic load and stores:他们说关于原子负载和存储:

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. That is, once the atomic load is completed, thread B is guaranteed to see everything thread A wrote to memory.如果线程 A 中的原子存储标记为 memory_order_release,并且线程 B 中来自同一变量的原子加载标记为 memory_order_acquire,则所有 memory 写入(非原子和松弛原子)从线程的角度来看发生在原子存储之前A,在线程 B 中成为可见的副作用。也就是说,一旦原子加载完成,线程 B 保证可以看到线程 A 写入 memory 的所有内容。

Then regarding mutexes:然后关于互斥锁:

Mutual exclusion locks, such as std::mutex or atomic spinlock, are an example of release-acquire synchronization: when the lock is released by thread A and acquired by thread B, everything that took place in the critical section (before the release) in the context of thread A has to be visible to thread B (after the acquire) which is executing the same critical section.互斥锁,例如 std::mutex 或原子自旋锁,是释放-获取同步的一个例子:当锁被线程 A 释放并被线程 B 获取时,发生在临界区(释放之前)的所有事情在线程 A 的上下文中,线程 B(在获取之后)必须可见,它正在执行相同的关键部分。

The first paragraph seems to say that an atomic load and store (with memory_order_release , memory_order_acquire ) thread B is guaranteed to see everything thread A wrote.第一段似乎说原子加载和存储(使用memory_order_releasememory_order_acquire )线程 B 可以保证看到线程 A 写的所有内容 including non-atomic writes.包括非原子写入。

The second paragraph seems to suggest that a mutex works the same way, except the scope of what is visible to B is limited to whatever was wrapped in the critical section, is that an accurate interpretation?第二段似乎表明互斥体的工作方式相同,除了B 可见的 scope 仅限于包含在关键部分中的任何内容,这是一个准确的解释吗? or would every write, even those before the critical section be visible to B?还是每个写入,甚至是在关键部分之前的写入对 B 都是可见的?

I think the reason the cppreference quote about mutexes is written that way is due to the fact that if you're using mutexes for synchronization, all shared variables used for communication should always be accessed inside the critical section.我认为关于互斥体的 cppreference 引用以这种方式编写的原因是,如果您使用互斥体进行同步,则应始终在关键部分内访问所有用于通信的共享变量。

The 2017 standard says in 4.7.1: 2017 年标准在 4.7.1 中说:

a call that acquires a mutex will perform an acquire operation on the locations comprising the mutex.获取互斥锁的调用将对包含互斥锁的位置执行获取操作。 Correspondingly, a call that releases the same mutex will perform a release operation on those same locations.相应地,释放相同互斥锁的调用将对这些相同位置执行释放操作。 Informally, performing a release operation on A forces prior side effects on other memory locations to become visible to other threads that later perform a consume or an acquire operation on A.非正式地,对 A 执行释放操作会强制其他 memory 位置上的先前副作用对稍后在 A 上执行消费或获取操作的其他线程可见。

Update: I want to make sure I have a solid post because it is surprisingly hard to find this information on the web.更新:我想确保我有一个可靠的帖子,因为很难在 web 上找到这些信息。 Thanks to @Davis Herring for pointing me in the right direction.感谢@Davis Herring 为我指明了正确的方向。

The standard says标准说

in 33.4.3.2.11 and 33.4.3.2.25 :33.4.3.2.1133.4.3.2.25 中

mutex unlock synchronizes with subsequent lock operations that obtain ownership on the same object互斥锁解锁与获得同一 object 所有权的后续锁定操作同步

( https://en.cppreference.com/w/cpp/thread/mutex/lock , https://en.cppreference.com/w/cpp/thread/mutex/unlock ) https://en.cppreference.com/w/cpp/thread/mutex/lock,https ://en.cppreference.com/w/cpp/thread/mutex/unlock

in 4.6.16 :4.6.16中:

Every value computation and side effect associated with a full-expression is sequenced before every value computation and side effect associated with the next full-expression to be evaluated.与完整表达式关联的每个值计算和副作用在与要评估的下一个完整表达式关联的每个值计算和副作用之前排序

https://en.cppreference.com/w/cpp/language/eval_order https://en.cppreference.com/w/cpp/language/eval_order

in 4.7.1.9 :4.7.1.9中:

An evaluation A inter-thread happens before evaluation B if评估 A线程间发生在评估 B 之前,如果

4.7.1.9.1) -- A synchronizes-with B, or 4.7.1.9.1)——A与B同步,或

4.7.1.9.2) -- A is dependency-ordered before B, or 4.7.1.9.2) -- A 在 B 之前是依赖排序的,或者

4.7.1.9.3) -- for some evaluation X 4.7.1.9.3)——用于某些评估 X

4.7.1.9.3.1) ------ A synchronizes with X and X is sequenced before B, or 4.7.1.9.3.1) ------ A 与 X 同步并且 X 在 B 之前排序,或者

4.7.1.9.3.2) ------ A is sequenced before X and X inter-thread happens before B, or 4.7.1.9.3.2) ------ A 在 X 之前排序,并且 X 线程间发生在 B 之前,或者

4.7.1.9.3.3) ------ A inter-thread happens before X and X inter-thread happens before B. 4.7.1.9.3.3) ------ 线程间发生在 X 之前,而 X 线程间发生在 B 之前。

https://en.cppreference.com/w/cpp/atomic/memory_order https://en.cppreference.com/w/cpp/atomic/memory_order

  • So a mutex unlock B inter-thread happens before a subsequent lock C by 4.7.1.9.1.因此,在 4.7.1.9.1 的后续锁 C之前发生互斥锁 B 线程间
  • Any evaluation A that happens in program order before the mutex unlock B also inter-thread happens before C by 4.7.1.9.3.2在互斥锁解锁 B 之前按程序顺序发生的任何评估 A 也会在 C之前发生线程间的 4.7.1.9.3.2
  • Therefore after an unlock() guarantees that all previous writes, even those outside the critical section, must be visible to a matching lock() .因此,在unlock()保证所有先前的写入之后,即使是那些在临界区之外的写入,都必须对匹配的lock()可见。

This conclusion is consistent with the way mutexes are implemented today (and were in the past) in that all program-order previous loads and stores are completed before unlocking.这个结论与今天(和过去)互斥锁的实现方式是一致的,因为所有程序顺序先前的加载和存储都是在解锁之前完成的。 (More accurately, the stores have to be visible before the unlock is visible when observed by a matching lock operation in any thread.) There's no question that this is the accepted definition of release in theory and in practice. (更准确地说,当通过任何线程中的匹配锁操作观察到解锁可见之前,存储必须可见。)毫无疑问,这是在理论上和实践中公认的释放定义。

There's no magic here: the mutex section is merely describing the common case, where (because every visit to the critical section might write the shared data) the writer in question protects all its access with the mutex.这里没有什么神奇之处:互斥锁部分只是描述常见情况,其中(因为每次访问临界区都可能写入共享数据)有问题的写入器使用互斥锁保护其所有访问。 (Other, earlier writes are visible and might be relevant: consider creating and initializing an object without synchronization and then storing its address in a shared variable in the critical section.) (其他,较早的写入是可见的并且可能是相关的:考虑创建和初始化一个不同步的 object,然后将其地址存储在临界区的共享变量中。)

The first paragraph seems to say that an atomic load and store (with memory_order_release, memory_order_acquire) thread B is guaranteed to see everything thread A wrote.第一段似乎说原子加载和存储(使用 memory_order_release,memory_order_acquire)线程 B 可以保证看到线程 A 写的所有内容。 including non-atomic writes.包括非原子写入。

Not just writes , all memory operations are done;不只是写,所有的 memory 操作都完成了; you can see that reads are accomplished too: although of course a read doesn't produce a side effect, you can see that reads before the release never see a value written after the acquire.您可以看到读取也完成了:虽然读取当然不会产生副作用,但您可以看到在发布之前读取永远不会看到在获取之后写入的值。

All of https://en.cppreference.com/ insists on writes (easy to explain) and completely ignore the issue of reads being accomplished.所有https://en.cppreference.com/ 都坚持写入(易于解释),完全忽略了读取完成的问题。

The second paragraph seems to suggest that a mutex works the same way, except the scope of what is visible to B is limited to whatever was wrapped in the critical section, is that an accurate interpretation?第二段似乎表明互斥体的工作方式相同,除了 B 可见的 scope 仅限于包含在关键部分中的任何内容,这是一个准确的解释吗? or would every write, even those before the critical section be visible to B?还是每个写入,甚至是在关键部分之前的写入对 B 都是可见的?

But "in the critical section" isn't even a thing .但是“在关键部分”甚至不是一回事 Nothing you do can be separated from the memory state in which it's done.您所做的任何事情都不能与完成它的 memory state 分开。 When you set an integer object "in the critical section", the object has to exist;当您在“临界区”中设置 integer object 时,object 必须存在; it doesn't make sense to take "write to an object" is isolation as there would be no object to talk about.将“写入对象”视为隔离是没有意义的,因为没有 object 可谈。 Interpreted strictly, "the critical section" would cover only object created inside it.严格解释,“关键部分”将仅涵盖在其中创建的 object。 But then none of these objects would be known by other threads so there would be nothing to protect.但是这些对象中的任何一个都不会被其他线程知道,因此没有什么可以保护的。

So the result of "critical section" is by essence the whole history of the program , with some accesses to shared objects starting only after the mutex lock.所以“临界区”的结果本质上是程序的整个历史,只有在互斥锁之后才开始对共享对象的一些访问。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM