Являются ли фундаментальные типы C/C++ атомарными?


С/C++ фундаментальные типы, как int,double и т. д. атомная, например, ориентирована на многопотоковое исполнение?

свободны ли они от гонок данных; то есть, если один поток пишет в объект такого типа, а другой поток читает из него, хорошо ли определено поведение?

Если нет, то это зависит от компилятора или что-то еще?

4 57

4 ответа:

нет, основные типы данных (например,int,double) не являются атомарными, см. std::atomic.

вместо этого вы можете использовать std::atomic<int> или std::atomic<double>.

Примечание:std::atomic был введен с C++11, и я понимаю, что до C++11 стандарт C++ вообще не признавал существования многопоточности.


как указал @Josh,std::atomic_flag атомная булева типа. Это гарантированно без блокировки в отличие от std::atomic специализации.


цитируемая документация:http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf. я уверен, что стандарт не является бесплатным, и поэтому это не окончательная/официальная версия.

1.10 многопоточные исполнения и гонки данных

  1. два выражения оценки конфликта, если один из них изменяет память местоположение (1.7), а другой считывает или изменяет то же самое местоположение памяти.
  2. библиотека определяет ряд атомарных операций (пункт 29) и операций над мьютексами (пункт 30), которые специально определены как операции синхронизации. Эти операции играют особую роль в том, чтобы сделать назначения в одном потоке видимыми для другого. Операция синхронизации в одной или нескольких ячейках памяти является либо операцией потребления, либо операцией получения, либо операцией освобождения, либо обеими приобретите и выпустите деятельность. Операция синхронизации без связанной ячейки памяти является ограждением и может быть либо ограждением получения, либо ограждением освобождения, либо как ограждением получения, так и ограждением освобождения. Кроме того, существуют расслабленные атомарные операции, которые не являются операциями синхронизации, и атомарные операции чтения-изменения-записи, которые имеют специальные характеристики.


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

29.5 атомарные типы

  1. должны быть явные специализации атомарного шаблона для интегральных типов " char,signed char,unsigned char,short,unsigned short,int,unsigned int,long,unsigned long,long long,unsigned long long,char16_t,char32_t,wchar_t, и любые другие типы, необходимые typedefs в заголовке <cstdint>. Для каждого интегрального типа Интеграл, специализация atomic<integral> обеспечивает дополнительные атомарные операции подходит для интегральных типов. Должна быть специализация atomic<bool>, который обеспечивает общие атомарные операции, как указано в 29.6.1..


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

29.7 тип флага и операции

  1. операции над объектом типа atomic_flag должны быть без блокировки. [ Примечание: следовательно, операции также должны быть безадресными. Ни один другой тип не требует операций без блокировки, поэтому тип atomic_flag является минимальным аппаратно-реализованным типом, необходимым для соответствия этому международному стандарту. Остальные типы могут быть эмулированы с помощью atomic_flag, хотя и с менее идеальными свойствами. - Конечная нота ]

поскольку C также (в настоящее время) упоминается в вопросе, несмотря на то, что он не находится в тегах,Стандарт C гласит:

выполнение программы 5.1.2.3

...

когда обработка абстрактной машины прерывается поступлением сигнала, значения объектов, которые не являются атомарными без блокировки объекты, ни типа volatile sig_atomic_t не указаны, как и состояние среды с плавающей запятой. Этот значение любого объекта изменено обработчиком, который не является ни атомарным объектом без блокировки, ни типа volatile sig_atomic_t становится неопределенным, когда обработчик завершает работу, как и состояние среды с плавающей запятой, если это так изменено обработчиком и не восстановлено в исходное состояние.

и

5.1.2.4 многопоточные исполнения и гонки данных

...

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

[несколько страниц стандартов-некоторые абзацы, явно относящиеся к атомарным типам]

выполнение программы содержит гонки данных если он содержит два конфликтующих действия в разных потоках, по крайней мере один из которых не является атомарным, и ни один не происходит перед другим. какие такие результаты гонки данных в неопределенном поведении.

что такое атомный?

атомарный, как описание чего-то со свойством атома. Слово "атом" происходит от латинского атомус что означает "единовластие".

атомарная операция всегда неделима.

т. е. он выполняется неделимым образом, я считаю, что это то, что OP относится к "threadsafe". В смысле операция происходит мгновенно при просмотре другим потоком.

например, следующая операция, скорее всего, разделена (зависит от компилятора / оборудования):

i += 1;

потому что это может наблюдаться другим потоком (на гипотетическом оборудовании и компиляторе) как:

load r1, i;
addi r1, #1;
store i, r1;

два потока делают вышеуказанную операцию i += 1 без соответствующей синхронизации может привести к неправильному результату. Скажи i=0 изначально, thread T1 нагрузки T1.r1 = 0, и нить T2 нагрузки t2.r1 = 0. Оба потока увеличивают их соответствующие r1s на 1, а затем сохранить результат на i. Хотя было выполнено два приращения, значение i по-прежнему только 1, потому что операция приращения была делимой. Обратите внимание, что была синхронизация до и после i+=1 другой поток дождался бы завершения операции и таким образом наблюдал бы неразделенную операцию.

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

i = 3;

store i, #3;

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

атомарная операция гарантирует семантику упорядочения памяти.

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

для например, под "что-если" правило компилятору разрешено переупорядочивать хранилища и загрузки по своему усмотрению до тех пор, пока весь доступ к энергозависимой памяти происходит в порядке, указанном программой "как если бы" программа оценивалась в соответствии с формулировкой в стандарте. Таким образом, неатомные операции могут быть переупорядочены, нарушая любые предположения о порядке выполнения в многопоточной программе. Вот почему, казалось бы, невинное использование сырья int как сигнальная переменная в многопоточное программирование нарушается, даже если записи и чтения могут быть неделимыми, порядок может нарушить программу в зависимости от компилятора. Атомарная операция обеспечивает упорядочение операций вокруг нее в зависимости от того, какая семантика памяти указана. Смотрите std::memory_order.

процессор может также переупорядочить доступ к памяти в соответствии с ограничениями упорядочения памяти этого процессора. Ограничения упорядочения памяти для архитектуры x86 можно найти в разделе руководство разработчика программного обеспечения Intel 64 и ia32 Architectures раздел 8.2, начиная со страницы 2212.

примитивные типы (int,char etc) не являются атомарными

я надеюсь, что это объясняет почему примитивные типы не являются атомарными.

дополнительная информация, которую я не видел, упоминается в других ответах до сих пор:

если вы используете std::atomic<bool>, например,bool фактически является атомарным в целевой архитектуре, тогда компилятор не будет генерировать избыточные ограждения или блокировки. Тот же код будет сгенерирован как для простого bool.

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