Предположим, что пользователь скручивает ручку на MIDI-контроллере, и значения отправляются в мою программу с возрастанием и уменьшением до сохраненного значения. Скручивая ручку в одну сторону, вы отправите серию декрементов, их значение зависит от скорости вращения; скручивая другой шаг. Я хочу сохранить сохраненное значение (и значение, испускаемое следующей функцией), между 0 и 100. Если одно или несколько сообщений будут удалены, это не имеет большого значения, но я не хочу, чтобы неожиданные серьезные изменения в значение, испускаемое функцией OffsetResult_
.Каким должен быть порядок памяти для последовательной атомарности при хранении при определенных ошибках?
Мой вопрос тогда есть - следующие директивы заказа памяти выглядят правильно? Самый ясный для меня - compare_exchange_strong
. Программа использует это как store
, который может выйти из строя, поэтому кажется, что порядок выпуска памяти памяти применяется.
Могу ли я даже пойти на std::memory_order_relaxed
, поскольку основная проблема заключается в простоте изменений в храненииV, а не в запоминании каждого изменения в сохраненныхV?
Есть ли общий способ взглянуть на комбинированные функции загрузки/хранения, чтобы определить, следует ли его приобретать, выпускать или последовательно согласовывать?
class ChannelModel {
ChannelModel():currentV{0}{};
int OffsetResult_(int diff) noexcept;
private:
atomic<int> storedV;
};
int ChannelModel::OffsetResult_(int diff) noexcept {
int currentV = storedV.fetch_add(diff, std::memory_order_acquire) + diff;
if (currentV < 0) {//fix storedV unless another thread has already altered it
storedV.compare_exchange_strong(currentV, 0, std::memory_order_release, std::memory_order_relaxed);
return 0;
}
if (currentV > 100) {//fix storedV unless another thread has already altered it
storedV.compare_exchange_strong(currentV, 100, std::memory_order_release, std::memory_order_relaxed);
return 100;
}
return currentV;
}
Обратите внимание, что фактический код является гораздо более сложным, и разумно предположить, что ответ на каждое сообщение от контроллера будет принимать достаточно долго, что эта функция, в некоторых случаях, можно назвать двумя нитями в почти в то же время.
1. 'fetch_add (...) + diff' добавляет' diff' дважды, не так ли? 2. Не будет ли это 'fetch_add' результатом' storedV = storedV + diff', который может быть '< 0' or '> 100', который, в свою очередь, может быть видимым для других потоков? – Pixelchemist
fetch_add возвращает * исходное значение * и сохраняет * результат * добавления. Таким образом, чтобы иметь возможность правильно использовать результат в функции, не выполняя другую выборку для атома (и, возможно, имея проблемы параллелизма), я добавляю diff к вычисленному значению. – rsjaffe
Да, значение out of bounds может быть видимым для других потоков, но это не является серьезной проблемой, так как функция никогда не вернет значение за пределами границ. Таким образом, пользователь не заметит проблемы, кроме, может быть, очень небольшой задержки при запуске контроллера в противоположном направлении. Моя цель состоит в том, чтобы каждый поток имел внутренне согласованное представление о значении, поэтому основная часть работы выполняется с помощью временной переменной. – rsjaffe