Атомность в C ++: миф или реальность - PullRequest
33 голосов
/ 15 февраля 2011

Я читал статью о Программе без блокировки в MSDN. Там написано:

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

И это приводит несколько примеров:

// This write is not atomic because it is not natively aligned.
DWORD* pData = (DWORD*)(pChar + 1);
*pData = 0;

// This is not atomic because it is three separate operations.
++g_globalCounter;

// This write is atomic.
g_alignedGlobal = 0;

// This read is atomic.
DWORD local = g_alignedGlobal;

Я прочитал множество ответов и комментариев, говоря, что в C ++ нет гарантированности быть атомарным и даже не упоминается в стандартах, в SO, и теперь я немного запутался. Я неправильно истолковываю статью? Или автор статьи говорит о вещах, которые не являются стандартными и характерными для компилятора MSVC ++?

Так что согласно статье нижеприведенные назначения должны быть атомарными, верно?

struct Data
{
    char ID;
    char pad1[3];
    short Number;
    char pad2[2];
    char Name[5];
    char pad3[3];
    int Number2;
    double Value;
} DataVal;

DataVal.ID = 0;
DataVal.Number = 1000;
DataVal.Number2 = 0xFFFFFF;
DataVal.Value = 1.2;

Если это так, означает ли замена Name[5] и pad3[3] на std::string Name; разницу в выравнивании памяти? Будут ли присвоения переменным Number2 и Value все еще атомарными?

Может кто-нибудь объяснить, пожалуйста?

Ответы [ 8 ]

29 голосов
/ 15 февраля 2011

Эта рекомендация зависит от архитектуры. Это верно для x86 и x86_64 (в низкоуровневом программировании). Вы также должны убедиться, что компилятор не переупорядочивает ваш код. Для этого вы можете использовать «барьер памяти компилятора».

Низкоуровневое атомарное чтение и запись для x86 описано в Справочных руководствах Intel "Руководство разработчика программного обеспечения для архитектуры Intel® 64 и IA-32", том 3A (http://www.intel.com/Assets/PDF/manual/253668.pdf), раздел 8.1.1

8.1.1 Гарантированные атомные операции

Процессор Intel486 (и более новые процессоры с тех пор) гарантирует, что следующее Основные операции с памятью всегда будут выполняться атомарно:

  • Чтение или запись байта
  • Чтение или запись слова, выровненного по 16-битной границе
  • Чтение или запись двойного слова, выровненного по 32-битной границе

Процессор Pentium (и более новые процессоры с тех пор) гарантирует, что следующее дополнительные операции с памятью всегда будут выполняться атомарно:

  • Чтение или запись четырех слов, выровненных по 64-битной границе
  • 16-битный доступ к не кэшированным областям памяти, которые вписываются в 32-битную шину данных

Процессоры семейства P6 (и более новые процессоры с тех пор) гарантируют, что следующее дополнительная операция с памятью всегда будет выполняться атомарно:

  • Нераспределенные 16-, 32- и 64-битные обращения к кешируемой памяти, которые помещаются в кеш линия

В этом документе также приведено более подробное описание для более новых процессоров, таких как Core2. Не все невыровненные операции будут атомарными.

Другое руководство по Intel рекомендует этот документ:

http://software.intel.com/en-us/articles/developing-multithreaded-applications-a-platform-consistent-approach/

12 голосов
/ 15 февраля 2011

Я думаю, что вы неверно истолковываете цитату.

Атомность может быть гарантирована для данной архитектуры с использованием специальных инструкций (соответствующих этой архитектуре). В статье MSDN объясняется, что чтение и запись для встроенных типов C ++ могут быть атомарными для x86 архитектуры.

Однако стандарт C ++ не предполагает, что такое архитектура, поэтому Стандарт не может давать такие гарантии. Действительно, C ++ используется во встроенном программном обеспечении, где аппаратная поддержка гораздо более ограничена.

C ++ 0x определяет шаблонный класс std::atomic, который позволяет преобразовывать операции чтения и записи в атомарные операции любого типа. Компилятор выберет лучший способ получения атомарности на основе характеристик типа и архитектуры, нацеленной стандартным образом.

Новый стандарт также определяет целый ряд операций, аналогичных MSVC InterlockExchange, который также скомпилирован с самыми быстрыми (но безопасными) доступными примитивами, предлагаемыми аппаратным обеспечением.

3 голосов
/ 15 февраля 2011

Стандарт c ++ не гарантирует атомарного поведения.На практике, однако, простые операции загрузки и сохранения будут атомарными, как говорится в статье.

Если вам нужна атомарность, то лучше прояснить это и использовать некоторую блокировку.*

*counter = 0; // this is atomic on most platforms
*counter++;   // this is NOT atomic on most platforms
2 голосов
/ 15 февраля 2011

Будьте очень осторожны, полагаясь на атомарность простых операций с размером слова, потому что вещи могут вести себя не так, как вы ожидаете. На многоядерных архитектурах вы можете быть свидетелями неупорядоченного чтения и записи. Это потребует барьеров памяти для предотвращения. (подробнее здесь ).

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

1 голос
/ 15 февраля 2011

Я думаю, atomicity, как указано в статье, имеет мало практического применения. Это означает, что вы будете читать / записывать правильное значение, но, вероятно, устарели. Поэтому, читая int, вы прочитаете его полностью, а не 2 байта из старого значения и 2 других байта из нового значения, которое в данный момент записывается другим потоком.

Для разделяемой памяти важны барьеры памяти. И они гарантированы примитивами синхронизации, такими как типы C ++ 0x atomic, mutexes и т. Д.

1 голос
/ 15 февраля 2011

Я думаю, что они пытаются объяснить, что типы данных, изначально реализованные аппаратным обеспечением, обновляются в аппаратном обеспечении таким образом, что чтение из другого потока никогда не даст вам «частично» обновленное значение.

Рассмотрим 32-битное целое число на 32-битной машине.Он записывается или читается полностью в 1 цикле инструкций, в то время как типы данных больших размеров, скажем, 64-битное int на 32-битной машине, потребуют больше циклов, следовательно, теоретически поток, записывающий их, может быть прерван междуэти циклы, следовательно, значение не в допустимом состоянии.

Никакая используемая строка не сделает ее атомарной, так как строка является конструкцией более высокого уровня и не реализована в оборудовании.Редактировать: Согласно вашему комментарию о том, что вы (не) имели в виду при переходе на строку, это не должно иметь никакого значения для полей, объявленных после, как уже упоминалось в другом ответе, компилятор будет выравнивать поля по умолчанию.

Причинадело не в том, что, как указано в статье, речь идет о том, как современные процессоры реализуют инструкции.Ваш стандартный код C / C ++ должен работать точно так же на 16- или 64-битной машине (только с разницей в производительности), однако, если вы предполагаете, что будете работать только на 64-битной машине, то все, что 64-битное или меньше, является атомарным.(SSE и т. Д. В сторону)

1 голос
/ 15 февраля 2011

IMO, статья содержит некоторые предположения о базовой архитектуре.Поскольку C ++ имеет только некоторые минималистические требования к архитектуре, в стандарте не может быть дано никаких гарантий, например, об атомарности.Например, байт должен быть не менее 8 бит, но у вас может быть архитектура, в которой байт равен 9 битам, а теоретически - 16 ...

Поэтому, когда компилятор специфичен для архитектуры x86,можно использовать определенные функции.

Примечание: структуры обычно по умолчанию выровнены по границе собственных слов.Вы можете отключить это с помощью операторов #pragma, поэтому заполнение отступов не требуется

0 голосов
/ 15 февраля 2011

Я не думаю, что изменение char Name[5] на std::string Name будет иметь значение , если вы используете его только для индивидуальных назначений символов , поскольку оператор индекса будет возвращать прямую ссылку на базовый символ.Полное строковое присваивание не является атомарным (и вы не можете сделать это с массивом символов, поэтому я предполагаю, что вы все равно не думали использовать его таким образом).

...