Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

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.

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. 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? or would every write, even those before the critical section be visible to B?

like image 497
Lockyer Avatar asked Sep 20 '19 03:09

Lockyer


People also ask

What is the role of std :: mutex?

std::mutex A mutex is a lockable object that is designed to signal when critical sections of code need exclusive access, preventing other threads with the same protection from executing concurrently and access the same memory locations.

Are mutex locks fair?

On windows e.g. mutexes are mostly fair, but not always. Some implementations e.g. Thread Building Block provide special mutexes that are fair, but these are not based on the OSes native mutexes, and are usually implemented as spin-locks (which have their own caveats).

Is std :: mutex thread safe?

It is totally safe for multiple threads to read the same variable, but std::mutex can not be locked by multiple threads simultaneously, even if those threads only want to read a value. Shared mutexes and locks allow this.

Should a mutex be global?

Mutexes are in a sense global, regardless of how you make them available. They're shared between disparate pieces of code. So you can make them globals, or you can pass them down the call chain, through functions that don't use them, until you eventually reach someone who does.

Does mutex require a critical_section?

In short, std::mutex does not use a CRITICAL_SECTION at all, instead using the CRT's special critical_section implementation (which uses the Win32 API directly to implement a mutex with a waiting list and all the trimmings). Edited bycameron3141Friday, January 9, 2015 7:13 PM

What happens when a mutex is not owned by a thread?

When the thread no longer needs to own the mutex object, it calls the ReleaseMutex function so that another thread can acquire ownership. A thread can specify a mutex that it already owns in a call to one of the wait functions without blocking its execution.

How does mutex work in Java?

The mutex class is a synchronization primitive that can be used to protect shared data from being simultaneously accessed by multiple threads. A calling thread owns a mutex from the time that it successfully calls either lock or try_lock until it calls unlock .

How do I specify a mutex that I already own?

A thread can specify a mutex that it already owns in a call to one of the wait functions without blocking its execution. This prevents a thread from deadlocking itself while waiting for a mutex that it already owns.


2 Answers

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.

The 2017 standard says in 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.

Update: I want to make sure I have a solid post because it is surprisingly hard to find this information on the web. Thanks to @Davis Herring for pointing me in the right direction.

The standard says

in 33.4.3.2.11 and 33.4.3.2.25:

mutex unlock synchronizes with subsequent lock operations that obtain ownership on the same object

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

in 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

in 4.7.1.9:

An evaluation A inter-thread happens before evaluation B if

4.7.1.9.1) -- A synchronizes-with B, or

4.7.1.9.2) -- A is dependency-ordered before B, or

4.7.1.9.3) -- for some evaluation X

4.7.1.9.3.1) ------ A synchronizes with X and X is sequenced before B, or

4.7.1.9.3.2) ------ A is sequenced before X and X inter-thread happens before B, or

4.7.1.9.3.3) ------ A inter-thread happens before X and X inter-thread happens before B.

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.
  • 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
  • Therefore after an unlock() guarantees that all previous writes, even those outside the critical section, must be visible to a matching 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.

like image 96
Humphrey Winnebago Avatar answered Oct 12 '22 00:10

Humphrey Winnebago


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.)

like image 39
Davis Herring Avatar answered Oct 12 '22 00:10

Davis Herring