Каким должен быть порядок памяти для последовательной загрузки атома, когда допустимы определенные ошибки

Предположим, что пользователь крутит ручку на контроллере MIDI, и значения отправляются в мою программу в виде приращений и уменьшений до сохраненного значения. Поворот ручки в одну сторону пошлет серию уменьшений, их значение зависит от скорости вращения; скручивание приращений в другую сторону. Я хочу, чтобы сохраненное значение (и значение, испускаемое следующей функцией) находилось в диапазоне от 0 до 100. Если одно или несколько сообщений будут отброшены, это не страшно, но я не хочу, чтобы неожиданные серьезные изменения в значение, испускаемое OffsetResult_ функция.

Мой вопрос тогда — следующие директивы порядка памяти выглядят правильно? Самым ясным для меня является compare_exchange_strong, Программа использует это как store это может привести к сбою, поэтому кажется, что применяется порядок освобождения памяти.

Могу ли я даже пойти в std::memory_order_relaxed так как основная проблема — просто атомарность изменений в StorageV, а не запоминание каждого изменения в StoreV?

Есть ли общий способ взглянуть на комбинированные функции загрузки / сохранения, чтобы увидеть, должны ли они быть получены, освобождены или последовательно согласованы?

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;
}

Обратите внимание, что реальный код намного сложнее, и разумно полагать, что ответ на каждое сообщение от контроллера займет достаточно много времени, и иногда эта функция будет вызываться двумя потоками практически одновременно.

2

Решение

Я сделаю предположение, что currentV является локальной переменной в OffsetResult_,
По какой-то причине он инициализируется в конструкторе класса, но не определяется как переменная класса.

Вы меняете значение storedV с fetch_add а затем скорректировать возможные ошибки с compare_exchange_strong,
Это не правильно … compare_exchange_strong используется здесь как условный store, Только если другой поток не изменяет значение, storedV будет обновлено.
Порядок памяти, который вы указываете, неправильный. В общем, release упорядочение используется с атомным store чтобы указать, что данные «выпущены»,
то есть. сделано доступным для другой темы, которая будет load из того же атомного использования acquire упорядоченность. release а также acquire Заказ формируется во время выполнения отношений и всегда приходит парами.
Это отношение отсутствует в вашем коде, когда currentV находится в заданном диапазоне, так как вы никогда не выполняете release операция.

Не понятно, почему вы хотите указать заказы. Обратите внимание, что вам не нужно устанавливать порядок в памяти, в этом случае (безопаснее) по умолчанию (std::memory_order_seq_cst) будет использоваться.
Правильность более слабого порядка зависит от данных, которые он синхронизирует между потоками.
Без зависимости от данных, используя std::memory_order_relaxed может быть правильным, но этот контекст отсутствует в коде.
Однако, поскольку атомарный элемент связан со значением ручки, вероятно, что поворот ручки приведет к некоторым действиям, связанным с другими данными.
Я не буду пытаться оптимизировать с более слабыми упорядочениями памяти здесь. Скорее всего, не будет никакой выгоды, так как вызов Read-Modify-Write (compare_exchange_x) уже
относительно дорого Кроме того, если использование более слабого порядка в памяти приводит к ошибке, это будет очень трудно отлаживать.

Ты можешь использовать std::compare_exchange_weak для настройки без потери обновлений:

int ChannelModel::OffsetResult_(int diff) noexcept {
int updatedV;

int currentV = storedV.load();

do {
updatedV = currentV + diff;

if (updatedV > 100)
updatedV = 100;
else if (updatedV < 0)
updatedV = 0;

} while (!storedV.compare_exchange_weak(currentV, updatedV));

return updatedV;
}

Ключ в том, что compare_exchange_weak будет только (атомарно) обновлять storedV если он все еще (или снова) равен currentV,
Если эта проверка не пройдена, она будет повторять цикл снова.
Используется в цикле, compare_exchange_weak (который может неуспешно провалиться) является лучшим выбором, чем compare_exchange_strong,

Упорядочение памяти — сложная тема, Вот хороший обзор

0

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector