2017-02-02 7 views
1

Основываясь на previous question, мне было интересно, если следующий код будет работать, чтобы вычислить нижние и верхние границы для среднего значения свойства измеряются с помощью Atomics:Release Приобретать Семантику для вычисления нижнего и верхней границей Средней

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 обновления, выпущенные перед обновлением он Seing в m_accLower из-за расцепления приобретают на этом последнем поле, но, возможно, некоторые другие. Двойной случай встречается в GetAverageUpper().

Справедливо ли это? Гарантируется ли, что нижняя версия на самом деле всегда имеет нижнюю границу, а верхняя - верхняя граница среднего?

Если эти методы действительно делают то, что я ожидал, то фактическая средняя будет находиться в замкнутом интервале: [GetAverageLower() , GetAverageUpper()]

ответ

2

Прежде чем я отвечу на этот вопрос, я чувствую необходимость заявить что-то:
Просто используя (расслабленную) атоматику, вам гарантировано, что один поток увидит изменение атома, которое произошло в других потоках. переупорядочение памяти не связано с видимостью. речь идет о предотвращении компилятора и процессора от скремблирования строк кода.

Теперь, когда мы установили, что существует проблема, если вы звоните 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); 

Что, конечно же, нарушает вашу кодовую логику.

+0

С таким переупорядочением 'm_accUpper' в конечном итоге будет« более обновлено », чем' m_countUpper', а затем 'inlinedValue' будет больше, чем в противном случае. Но это нормально! Это ожидаемое поведение, и это то, что я имел в виду, когда я суффикс этих элементов с помощью '* Upper' -' GetAverageUpper() 'разрешено возвращать значение, превышающее фактическое среднее значение, в то время как' GetAverageLower() 'может возвращать меньшее значение , – Tarc

Смежные вопросы