На основе предыдущий вопрос, Мне было интересно, будет ли следующий код работать для вычисления нижних и верхних границ для среднего значения свойства, измеряемого с использованием атомики:
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;
}
Предположим, что нижнее и верхнее обновления всегда выпускаются вместе, и одновременных обновлений нет.
Функция GetAverageLower()
гарантированно увидит любой m_countLower
обновления выпускаются прямо перед обновлением m_accLower
из-за релиза-приобретения на этом последнем поле, но, может быть, еще немного. Двойственный случай происходит в GetAverageUpper()
,
Правильно ли это мышление? Гарантируется ли, что нижняя версия всегда получает нижнюю границу, а верхняя — верхнюю границу среднего?
Если эти методы действительно делают то, что я ожидаю, тогда фактическое среднее значение будет в закрытом интервале: [GetAverageLower() , GetAverageUpper()]
Прежде чем я отвечу на этот вопрос, я чувствую необходимость заявить кое-что:
Просто используя (расслабленную) атомарность, вы гарантируете, что один поток увидит изменение в атомарном, которое произошло в других потоках. переупорядочение памяти не о видимости. Речь идет о предотвращении компиляции и процессора от шифрования строк кода.
Теперь, когда мы это установили, возникает проблема, если вы позвоните GetAverageUpper
а потом в UpdateUpper
в той же теме. после встраивания объединенный код будет выглядеть так:
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 );
Теперь компилятор / процессор не может переупорядочивать любые строки, которые предшествуют acquire
и любые строки кода, которые идут после release
но как насчет двух relaxed
в середине? они могут быть переупорядочены:
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 );
Что, конечно, нарушает вашу логику кода.
Других решений пока нет …