Post by Chris M. ThomassonPost by Chris M. ThomassonPost by Bonita Montero#include <Windows.h>
#include <thread>
using namespace std;
int main()
{
constexpr size_t ROUNDS = 1'000'000;
size_t volatile r = 1'000'000;
jthread thr( [&]()
{
while( r )
SleepEx( INFINITE, TRUE );
} );
for( size_t r = ROUNDS; r--; )
QueueUserAPC( (PAPCFUNC)[]( auto p ) { --*(size_t*)p; },
thr.native_handle(), (ULONG_PTR)&r );
}
std::atomic<size_t> r
I'm confused. Does std:atomic imply "do not optimize access to this
variable"? Because if it doesn't, then I can see how the "while (r)"
loop can just spin.
std::atomic should honor a read, when you read it even from
std::memory_order_relaxed. If not, imvvvvhhooo, its broken?
You will probably find that compilers in practice will re-read "r" each
round of the loop, regardless of the memory order. I am not convinced
this would be required for "relaxed", but compilers generally do not
optimise atomics as much as they are allowed to. They are, as far as I
have seen in my far from comprehensive testing, treated as though
"atomic" implied "volatile".
But as far as I can tell from the C and C++ standards, "atomic" does not
imply "volatile". There are situations where atomics can be "optimised"
- re-ordered with respect to other code, or simplified - while volatile
atomics cannot. I can see no reason why adjacent non-volatile relaxed
atomic reads of the same object cannot be combined, even if separated by
other code (with no volatile or atomic accesses). The same goes for
writes. If you have :
std::atomic<int> ax = 100;
...
x = 1;
x += 2;
x = x * x;
then you are guaranteed that any other thread reading "ax" will see
either the old value (100, if it was not changed), or the final value of
9. It /might/ also see values of 1 or 3 along the way, but there is no
requirement for the code to produce these intermediate values or for
them to be visible to other threads.
At least, that is how I interpret things. And I believe the fact that
the C and C++ standards make a distinction between atomics and volatile
atomics indicates that the standard authors do not see "atomic" as
implying the semantics of "volatile" - even if compiler writers choose
to act that way.
I personally thing it was a terrible mistake to mix sequencing and
ordering with atomics when multi-threading was introduced to the C and
C++ standards. Atomics would have been simpler, more efficient, and
consistent with their naming if their semantics had not included any
kind of synchronisation. Synchronisation and ordering is a very
different concept from atomic access, and should be covered differently
(by fences of various sorts).