If I have 2 threads that execute f()
and f2()
, like this:
std::atomic_flag flag; // a member variable in a class
std::mutex m;
flag.test_and_set(); // set to true before f() or f2()
// executed only once (a "stop" function)
void f() {
flag.clear(std::memory_order_relaxed);
{
std::unique_lock<std::mutex> lock(m);
// do something
}
// join f2's thread
}
// long running function
void f2() {
std::unique_lock<std::mutex> lock(m);
while (flag.test(std::memory_order_relaxed)) {
// do stuff
}
}
f2()
is generally executed first, and f()
is supposed to stop f2()
.
It's important that flag.clear
executes before the mutex is acquired, or else f()
wouldn't return (in my actual case it's just that it would take a much longer time to finish).
So my question: Is relaxed memory ordering sufficient to guarantee that flag.clear()
executes before locking the mutex?
In this question, a similar scenario is presented. From what I can understand, it's safe to use a relaxed memory order there because there are no dependencies on the flag other then joining the thread, and std::thread
's join is a synchronizing operation.
So therefore flag.clear()
couldn't be moved after join
.
However here, I use a std::mutex
, but if I say switch to another implementation like this one: https://rigtorp.se/spinlock/, then would it still be safe to use a relaxed memory order? Because their lock
only uses memory order acquire, isn't it technically possible for the locking of the mutex to be ordered entirely before flag.clear
? As acquire only prevents operations after it from moving behind the acquire, but it doesn't say anything about stores moving after the acquire.
Would I need something like a StoreLoad barrier, like in here: https://preshing.com/20120710/memory-barriers-are-like-source-control-operations/? This question seems to suggest that I need a full memory barrier as well.
Or alternatively, if the flag.clear()
store operation could somehow have an "acquire" memory ordering, then it would prevent operations after from being reordered before the store. But this comment suggests that you need sequentially consistent ordering instead:
If you really need to prevent something from being reordered before a store, AFAIK your only recourse is to use seq_cst on both the store and the other operation. Nothing weaker suffices in the C++ memory model.