Как умножить два значения и сохранить результат атомарно? - PullRequest
8 голосов
/ 07 июля 2019

Скажем, в моем коде есть следующие глобальные переменные:

std::atomic<uint32_t> x(...);
std::atomic<uint32_t> y(...);
std::atomic<uint32_t> z(...);

Моя задача - умножить x и y, а затем сохранить результат в z:

z = x * y

Я знаю, что наивный подход вызова store () и load () для каждого объекта совершенно неверен:

z.store(x.load() * y.load()); // wrong

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

Я мог бы выбрать цикл сравнения и замены (CAS), но он гарантировал бы атомарность только для замены старого значения z на новое (x*y): я все еще не уверен, как выполнить всю операцию за один атомный шаг.

Мне также известно, что обертывание x, y и z внутри структуры и сделать его атомарным здесь неосуществимо, поскольку структура не помещается в один 64-битный регистр. Компилятор будет использовать блокировки под капотом (поправьте меня, если я здесь не прав).

Эта проблема разрешима только с мьютексом?

1 Ответ

4 голосов
/ 07 июля 2019

Я все еще не уверен, как выполнить всю операцию за один атомарный шаг.

Это будет возможно только в том случае, если ваша архитектура поддерживает что-то вроде "32-битное атомарное умножение "(и вам придется делать это вне возможностей стандарта C ++) или атомное ядро, достаточно широкое для выполнения операции RMW на 64-битных.

Я также знаючто обертывание x, y и z внутри структуры и сделать его атомарным здесь неосуществимо, поскольку структура не помещается в один 64-битный регистр.

Дажеесли они подойдут, вам все равно нужно будет выполнить операцию RMW, поскольку маловероятно, что у вас все равно есть атомное умножение.

Эта проблема разрешима только с мьютексом?

Если ваша архитектура поддерживает 64-разрядный элемент без блокировки (проверьте с помощью is_always_lock_free), вы можете хранить вместе x и y и выполнять операции с ним по мере необходимости.

что если переменныеuint64_t вместо этого, или операция была более сложной, например x * y * w * k / j?

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

Обратите внимание, что даже если вы воспринимаете некоторые операции как атомарныевы должны понимать, что в системе SMP вы все равно оказываете давление на иерархию кэша.

...