Являются ли фундаментальные типы C/C++ атомарными?
С/C++ фундаментальные типы, как int
,double
и т. д. атомная, например, ориентирована на многопотоковое исполнение?
свободны ли они от гонок данных; то есть, если один поток пишет в объект такого типа, а другой поток читает из него, хорошо ли определено поведение?
Если нет, то это зависит от компилятора или что-то еще?
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.7), а другой считывает или изменяет то же самое местоположение памяти.
- библиотека определяет ряд атомарных операций (пункт 29) и операций над мьютексами (пункт 30), которые специально определены как операции синхронизации. Эти операции играют особую роль в том, чтобы сделать назначения в одном потоке видимыми для другого. Операция синхронизации в одной или нескольких ячейках памяти является либо операцией потребления, либо операцией получения, либо операцией освобождения, либо обеими приобретите и выпустите деятельность. Операция синхронизации без связанной ячейки памяти является ограждением и может быть либо ограждением получения, либо ограждением освобождения, либо как ограждением получения, так и ограждением освобождения. Кроме того, существуют расслабленные атомарные операции, которые не являются операциями синхронизации, и атомарные операции чтения-изменения-записи, которые имеют специальные характеристики.
- два действия потенциально параллельны если
(23.1) - они выполняются разными потоками, или
(23.2) - они не упорядочены, и по крайней мере один выполняется обработчиком сигнала.
Выполнение программы содержит гонку данных, если она содержит два потенциально одновременных конфликтующих действия, по крайней мере одно из которых не является атомарным, и ни одно не происходит раньше другого, за исключением особого случая для обработчиков сигналов, описанных ниже. Любая такая гонка данных приводит к неопределенному поведению.29.5 атомарные типы
- должны быть явные специализации атомарного шаблона для интегральных типов " 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..
- должны быть частичные специализации указателя шаблона атомарного класса. Эти специализации должны иметь стандартную компоновку, тривиальные конструкторы по умолчанию и тривиальные деструкторы. Каждый из них должен поддерживать синтаксис агрегированной инициализации.
29.7 тип флага и операции
- операции над объектом типа 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
изначально, threadT1
нагрузкиT1.r1 = 0
, и нитьT2
нагрузкиt2.r1 = 0
. Оба потока увеличивают их соответствующиеr1
s на 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
только делает код менее эффективным, если это действительно требуется для корректности на платформе. Так что нет причин избегать оно.