2017-02-02 10 views
1

Скажем есть две функции для обновления и возвращения в среднем некоторого свойства измеряемого:Release Приобретать семантику Compute Среднее

void Class::Update(int delta) 
{ 
    m_accumulatedValue += delta; 
    ++ m_count; 
} 

double Class::GetAverage() 
{ 
    return m_accumulatedValue/(double)m_count; 
} 

Теперь предположим, что они должны быть изменены, чтобы работать в многопоточной среде с резьбой бассейн, в котором любой поток может быть предложено выполнить одно из них - то есть, поток выполнения каждого из них может быть другой один каждый раз, когда:

std::atomic<int> m_accumulatedValue; 
std::atomic<int> m_count; 

// ... 

void Class::Update(int delta) 
{ 
    m_accumulatedValue.fetch_add(delta , std::memory_order_relaxed); 
    m_count.fetch_add(1 , std::memory_order_release); 
} 

double Class::GetAverage() 
{ 
    auto count = m_count.load(std::memory_order_acquire); 
    auto acc = m_accumulatedValue.load(std::memory_order_relaxed); 

    return acc/(double)count; 
} 

Я пытаюсь понять, приобретаемой и освободить память упорядоченности.

Предположим, что одновременных вызовов одного и того же объекта нет для Update(), но могут быть одновременными вызовами на одном и том же объекте для Update() и GetAverage().

За то, что я прочитал, приобретаемая нагрузка m_count в GetAverage() запрещает изменение порядка загрузки m_accumulatedValue перед ним и в то же время гарантирует, что любые изменения в m_accumulatedValue исполнение Update() видно на резьбу призванию GetAverage() после изменения на m_count также видно, для магазина, выполненного на m_cout, Update() имеет заказ на выпуск.

Это то, что я только что сказал правильно?

GetAverage() (с указанной гарантией несовместимости звонков на Update()) всегда верный правильный ответ? Или может быть способ возврата среднего значения с некоторыми значениями, «более обновленными», чем другие?

Нужно ли быть атомарным m_accumulatedValue?

+1

'memory_order_relaxed' не запрещает другому потоку видеть новое значение, оно просто не требует его.Таким образом, все еще может быть состояние гонки. –

+2

Помимо семантики порядка памяти этот код не работает. Один из возможных вариантов выполнения состоит в том, что один поток добавляет к 'm_accumulatedValue', затем другой поток читает' m_accumulatedValue' ** и ** 'm_count', тогда первый поток обновляет' m_count'. Среднее значение, рассчитанное по второму потоку, будет неправильным. –

+0

Нет. Нет. Может быть. Нет. Почему бы не использовать взаимное исключение? – jotik

ответ

0

Ваше описание правильности работы семантики получения/выпуска; они используются для создания межпоточной связи происходит до между операциями памяти до хранения/выпуска и после загрузки/получения ... Это основано на соотношении времени выполнения и только определено, если атомная нагрузка/приобретает значение, заданное хранилищем/выпуском.

Первой проблемой с вашим кодом является то, что он не отвечает этому требованию времени выполнения. Значение m_count не проверяется, и поэтому гарантийные обязательства не распространяются; поэтому вы могли бы использовать memory_order_relaxed для всех операций.

Но это само по себе не решает проблему; когда вы читаете m_accumulatedValue, его значение может быть снова изменено другим вызовом на Update() (m_accumulatedValue поэтому должно быть атомарным). Более подробно, как указано в разделе комментариев, поскольку атомарные атомы отсутствуют, GetAverage() может быть вызван до завершения Update() и возврата неправильного значения.

Что вам нужно, это строгий порядок между Update() и GetAverage(), и лучший способ сделать это с помощью std::mutex. Атомные переменные могут быть регулярными целыми числами (если их не использовать в другом месте).