Скажем, есть две функции для обновления и возврата среднего значения некоторого измеряемого свойства:
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
нужно быть атомным вообще?
Ваше описание того, как работает семантика получения / выпуска, является правильным;
они используются для создания потока случается, перед тем связь между операциями с памятью до сохранения / выпуска и после загрузки / получения …
Это основано на отношениях во время выполнения и является только определяется, если атомная загрузка / получение видит значение, которое было установлено в хранилище / выпуске.
Первая проблема с вашим кодом заключается в том, что он не соответствует требованиям времени выполнения.
Значение m_count
не проверяется и, следовательно, гарантии заказа не применяются; поэтому вы могли бы также использовать memory_order_relaxed
на все операции.
Но это само по себе не решает проблему; когда ты читаешь m_accumulatedValue
его значение могло снова измениться при следующем вызове Update()
(m_accumulatedValue
поэтому должен быть атомным).
Кроме того, как указано в разделе комментариев, так как между атомарными операциями нет атомарности, GetAverage()
может быть вызван раньше Update()
закончен и возвращает неправильное значение.
То, что вам нужно, это строгий порядок между Update()
а также GetAverage()
и лучший способ сделать это с std::mutex
, Тогда атомарные переменные могут быть обычными целыми числами (если не используются где-либо еще).
Других решений пока нет …