Release Acquire Semantics to Compute Lower and Upper Bound of Average

泪湿孤枕 提交于 2019-12-11 09:22:06

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!