问题
Based on a previous question, I was wondering if the following code would work to compute lower and upper bounds to the average value of a property being measured using atomics:
std::atomic< unsigned int > m_accLower;
std::atomic< unsigned int > m_countLower;
std::atomic< unsigned int > m_accUpper;
std::atomic< unsigned int > m_countUpper;
// ...
void Class::UpdateLower( unsigned int delta )
{
m_countLower.fetch_add( 1 , std::memory_order_relaxed );
m_accLower.fetch_add( delta , std::memory_order_release );
}
double Class::GetAverageLower( )
{
auto acc = m_accLower.load( std::memory_order_acquire );
auto count = m_countLower.load( std::memory_order_relaxed );
return acc/(double)count;
}
void Class::UpdateUpper( unsigned int delta )
{
m_accUpper.fetch_add( delta , std::memory_order_relaxed );
m_countUpper.fetch_add( 1 , std::memory_order_release );
}
double Class::GetAverageUpper( )
{
auto count = m_countUpper.load( std::memory_order_acquire );
auto acc = m_accUpper.load( std::memory_order_relaxed );
return acc/(double)count;
}
Suppose the lower and upper updates are always issued together and there's no concurrent updates.
The function GetAverageLower()
is guaranteed to see any m_countLower
updates issued right before the update it's seing in m_accLower
due to the release-acquire on this last field, but maybe some more. The dual case occurs in GetAverageUpper()
.
Is this line of thought right? Is it guaranteed that the lower version actually gets always a lower bound and the upper version, an upper bound of the average?
If those methods are indeed doing what I expect, then the actual average would be in the closed interval: [GetAverageLower() , GetAverageUpper()]
回答1:
Before I'll answer this question, I feel the need to state something:
Just by using (relaxed) atomics you are guaranteed that one thread will see a change in atomic that occurred in other threads. memory reordering is not about visibility. it's about preventing the compiler and CPU from scrambling lines of code.
Now that we established that, there is an issue if you call GetAverageUpper
and then to UpdateUpper
in the same thread. after inlining, the merged code will look like that:
auto count = m_countUpper.load( std::memory_order_acquire );
auto acc = m_accUpper.load( std::memory_order_relaxed );
auto inlinedValue = acc/(double)count;
m_accUpper.fetch_add( delta , std::memory_order_relaxed );
m_countUpper.fetch_add( 1 , std::memory_order_release );
Now, the Compiler/CPU cannot reorder any lines which comes before acquire
and any lines of code which comes after release
, but what about the two relaxed
in the middle? they can be reordered:
auto count = m_countUpper.load( std::memory_order_acquire );
m_accUpper.fetch_add( delta , std::memory_order_relaxed );
auto acc = m_accUpper.load( std::memory_order_relaxed );
auto inlinedValue = acc/(double)count;
m_countUpper.fetch_add( 1 , std::memory_order_release );
Which of course breaks your code-logic.
来源:https://stackoverflow.com/questions/42008967/release-acquire-semantics-to-compute-lower-and-upper-bound-of-average