Атомарный Оператор Присваивания



Я использую очень легкую атомарную оболочку в качестве учебного упражнения для примитивных типов данных в C++ для Windows, и у меня есть несколько простых вопросов о реализации оператора присваивания. Рассмотрим две реализации ниже:



// Simple assignment
Atomic& Atomic::operator=(const Atomic& other)
{
mValue = other.mValue;
return *this;
}

// Interlocked assignment
Atomic& Atomic::operator=(const Atomic& other)
{
_InterlockedExchange(&mValue, other.mValue);
return *this;
}


Предположим, что mValue является правильным типом и чтоатомарный класс имеет его в качестве члена.




  1. требуется ли _InterlockedExchange для потокобезопасного оператора присваивания, или достаточно простой реализации, чтобы гарантировать потокобезопасность?

  2. Если простое присваивание потокобезопасно, то нужно ли вообще реализовывать оператор присваивания для этого класса? Компилятора по умолчанию должно хватить, не так ли?

  3. Если простое назначение является потокобезопасным в Windows, является ли оно также потокобезопасным и в других платформах? Требуется ли эквивалент _InterlockedExchange для обеспечения потокобезопасности на других платформах?

670   3  

3 ответов:

если mValue это примитивный тип (и не более 32 бит в ширину на 32-битном процессоре, не более 64 бит в ширину на 64-битном процессоре), и вы работаете на процессоре x86 (в 32-или 64-битном режиме) и Вы не меняете вручную данные, тогда чтение/запись памяти гарантированно будут атомарными.

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

Однако обратите внимание, что я говорю об атомарности, а не о потокобезопасности, потому что "потокобезопасность" зависит от контекста, в котором используется код.

Есть Три проблемы, с которыми сталкиваются атомарные типы C++. Во-первых, переключение потока может произойти в середине чтения или записи, что приводит к искажению данных; это известно как "разрыв". Во-вторых, каждый процессор имеет свой собственный кэш данных, и запись значения в одном потоке не обязательно обновляет значение в кэше другого процессора; это "устаревшие данные". В-третьих, компилятор может изменить порядок инструкций для повышения эффективности, если результаты не нарушают различные правила; если вы не говорите об этом то, что данные разделяются между потоками, может удивить вас.

Использование std::atomic (или различных специфичных для реализации механизмов обеспечения атомарности) решает все три проблемы. Нет никакой веской причиныобходить их; авторы библиотек и компиляторов почти наверняказнают лучше, чем вы, как создавать эффективный код, который работает правильно.

  1. это зависит от размера и выравнивания mValue, которое в дальнейшем зависит от того, как объект Atomic выровнен. Как правило, если размер равен размеру регистра процессора и правильно выровнен, то запись является атомарной. (Пример: 32-разрядный тип данных, выровненный по 32-разрядной границе на 32-разрядном процессоре, будет записан атомарно. Атомарность не гарантируется для 64-разрядного типа данных в этом сценарии.)
  2. Правильно, реализация компилятора по умолчанию будет идентична " простой пример присваивания", который вы привели, предполагая, что mValue является единственным членом данных экземпляра.
  3. обычно да, так как это скорее функция процессора и архитектуры, чем конкретной операционной системы. В тех случаях, когда запись mValue не будет атомарной, вам потребуется аналогичная конструкция. (Например, смотрите документациюGCC, касающуюся встроенных атомарных операций .)

Comments

    Ничего не найдено.