Say, I have two threads A
and B
writing to a global Boolean variables fA
and fB
respectively which are initially set to false
and are protected by std::mutex
objects mA
and mB
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 the modifications on fA
and fB
in different orders in different threads C
and D
? In other words, can the following 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 (i. e. 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();
}
legally print
Thread C: fA = 1, fB = 0
Thread D: fA = 0, fB = 1
according to the C++ standard?
Note: A spin-lock can be implemented using std::atomic<bool>
variables using either sequential consistent memory order or acquire/release memory order. So the question is whether an std::mutex
behaves like a sequentially consistent spin-lock or an acquire/release memory order spin-lock.