Re: atomic memory_order with command or with fence
On Sunday, 27 May 2012 17:08:33 UTC-4, itaj sherman wrote:
I'll put this code in functions, to clarify the context of x and r:
template< typename T >
void my_store_release_1( std::atomic<T>& x, T r )
{
x.store( r, memory_order_release );
}
template< typename T >
void my_store_release_2( std::atomic<T>& x, T r )
{
std::atomic_thread_fence( memory_order_release );
x.store( r, memory_order_relaxed );
}
Disclaimer: I am most certainly not an expert on this area, but based
on my current understanding on the topic, I believe these are not
the same. Hopefully someone, who has more experience, will clarify.
A fence or atomic store operation that is marked with
'memory_order_release' introduces inter-thread, happens-before
relationship on store operations that appear before the
'memory_order_release' fence or atomic store operation - given
it is paired with an acquire counterpart.
Conversely, it introduces no happens-before relationship on
operations that appear after the store / fence marked with
'memory_order_release', in regards their visibility in another
thread.
In this case the fence, marked with 'memory_order_release',
introduces no happens-before relationship on the store of x in
regards of the visibility of the store on x in another thread,
since the store appears after the fence.
I believe if you write:
template< typename T >
void my_store_release_3( std::atomic<T>& x, T r )
{
x.store( r, memory_order_relaxed );
std::atomic_thread_fence( memory_order_release );
}
Then 1 and 3 are equivalent, as far as the introduced inter-thread
happens-before relationship is concerned.
The same I would ask about load and acquire:
template< typename T >
T my_load_acquire_1( std::atomic<T>& x )
{
T const r = x.load( memory_order_relaxed );
std::atomic_thread_fence( memory_order_acquire );
return r;
}
template< typename T >
T my_load_acquire_2( std::atomic<T>& x )
{
T const r = x.load( memory_order_acquire );
return r;
}
Situation is similar here, the "memory_order_acquire" does not
impose happens-before relationship on the load to x, since the
load appears before the fence.
The correct way is:
template< typename T >
T my_load_acquire_3( std::atomic<T>& x )
{
std::atomic_thread_fence( memory_order_acquire );
T const r = x.load( memory_order_relaxed );
return r;
}
Of course by itself the load / store part is meaningless, the
acquire has to be paired with a release to see the complete picture.
In your example the order of operations vs. fence might have been
just an oversight, but I wanted to clarify, because it is very
important. As far as release-acquire semantic is concerned, I
believe that the newly written load_3/store_3 have the same effect
as 'my_store_release_1' (or store_3) paired with 'my_load_acquire_2'
(or load_3), as far as happens-before relationship is concerned.
I would risk to say that the only semantic difference is who
introduces the happens-before relationship, the fence(s), or the
atomic store / load operation(s).
PS: I am not sure if one is allowed to refer / advertise books,
but throughout discussion can be found on the on the C++11 memory
model in C++ Concurrency in Action book (Chapter 5) from Anthony
Williams, where he asks a very similar question with a very
similar example, and reaches the same conclusion. I can highly
recommend that book.
-- Zoltan
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]