Is std :: mutex a sequential sequence?

Let's say I have two streams A and B for writing to the global logical variables fA and fB respectively, which are initially set to false and protected by std::mutex mA and mB objects, respectively:

 // Thread A mA.lock(); assert( fA == false ); fA = true; mA.unlock(); // Thread B mB.lock() assert( fB == false ); fB = true; mB.unlock() 

Is it possible to observe changes on fA and fB in different orders in different flows C and D ? In other words, can the next program

 #include <atomic> #include <cassert> #include <iostream> #include <mutex> #include <thread> using namespace std; mutex mA, mB, coutMutex; bool fA = false, fB = false; int main() { thread A{ []{ lock_guard<mutex> lock{mA}; fA = true; } }; thread B{ [] { lock_guard<mutex> lock{mB}; fB = true; } }; thread C{ [] { // reads fA, then fB mA.lock(); const auto _1 = fA; mA.unlock(); mB.lock(); const auto _2 = fB; mB.unlock(); lock_guard<mutex> lock{coutMutex}; cout << "Thread C: fA = " << _1 << ", fB = " << _2 << endl; } }; thread D{ [] { // reads fB, then fA (ie vice versa) mB.lock(); const auto _3 = fB; mB.unlock(); mA.lock(); const auto _4 = fA; mA.unlock(); lock_guard<mutex> lock{coutMutex}; cout << "Thread D: fA = " << _4 << ", fB = " << _3 << endl; } }; A.join(); B.join(); C.join(); D.join(); } 

legal seal

 Thread C: fA = 1, fB = 0 Thread D: fA = 0, fB = 1 

according to C ++ standard?

Note. . Spin lock can be implemented using std::atomic<bool> variables using sequential sequential memory order or memory / free memory order. Therefore, the question is whether std::mutex leads as a sequential sequential spin-lock lock or fixing the memory order / freeing memory.

+10
source share
1 answer

Yes, it is allowed. Output is not possible, but std::mutex not necessarily sequentially sequential. Get / release enough to rule out this behavior.

std::mutex not defined in the standard for consistent consistency, only

30.4.1.2 Mutex Types [thread.mutex.requirements.mutex]

11 Synchronization: previous unlock () operations on the same object must be synchronized with (1.10) this operation [lock ()].

Synchronize-with seems to be defined the same way as std::memory_order::release/acquire (see this question ).
As far as I can see, the receive / release spinlock will conform to the standards for std :: mutex.

Great edit:

However, I do not think it means what you think (or what I thought). The conclusion is still impossible, since the semantics of receiving / issuing is enough to exclude it. This is a kind of subtle point that is best explained here . At first this seems impossible, but I think it’s right to be careful with such things.

From the standard unlock () is synchronized with lock (). This means that everything that happens before unlock () is visible after lock (). Happens earlier (hereinafter ->), this is a slightly strange attitude, which is better explained in the link above, but since in this example everything is mutually exclusive, everything works as you expect, i.e. const auto _1 = fA; occurs before const auto _2 = fB; and any changes visible to the thread when it unlock() mutex are visible to the next thread, which is the lock() mutex. It also has some expected properties, for example, if X occurs before Y, and Y - before Z, then X β†’ Z, also if X occurs before Y, Y does not happen before X.

From here it is not difficult to see a contradiction that seems intuitively correct.

In short, there is a well-defined order of operations for each mutex β€” for example, for mutex A, threads A, C, D hold locks in a certain sequence. For stream D to print fA = 0, it must block mA before stream A, and vice versa for stream C. Thus, the blocking sequence for mA is D (mA) β†’ A (mA) β†’ C (mA).

For mutex B, the sequence should be C (mB) β†’ B (mB) β†’ D (mB).

But from the program we know C (mA) β†’ C (mB), so that we can connect them together to get D (mA) β†’ A (mA) β†’ C (mA) β†’ C (mB) β†’ B (mB) β†’ D (mb), which means D (mA) β†’ D (mb). But the code also gives us D (mb) β†’ D (mA), which is a contradiction, meaning that your observable result is impossible.

This result is not different for capture / release spin locks, I think everyone confused regular memory access for capture / release for a variable with access to a variable protected by spin lock. The difference is that with the help of spin-lock, read streams also perform comparison / exchange and write of a release, which is a completely different scenario for writing a single issue and reading a read.

If you use a sequential sequential spin lock, this will not affect the result. The only difference is that you could always categorically answer questions like β€œmutex A was locked to mutex B” from a separate thread that did not receive any of the locks. But for this example and most others, such a statement is useless, therefore, acquisition / release is the standard.

+5
source

Source: https://habr.com/ru/post/1014408/


All Articles