Являются ли фундаментальные типы C/C++ атомарными?
являются фундаментальными типами C / C++, например int
, double
, etc., атомарный, например threadsafe?
свободны ли они от гонок данных; то есть, если один поток записывает в объект такого типа, а другой поток читает из него, хорошо ли определено поведение?
Если нет, зависит ли это от компилятора или чего-то еще?
4 ответов
нет, основные типы данных (например, int
, double
) не являются атомарными, см. std::atomic
.
вместо этого вы можете использовать std::atomic<int>
или std::atomic<double>
.
Примечание: std::atomic
был введен с C++11, и я понимаю, что до C++11 стандарт C++ вообще не признавал существования многопоточности.
как отметил @Josh,std::atomic_flag
атомная булева типа. Это гарантированно будет lock-free в отличие от std::atomic
специализации.
цитируемая документация из:http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf. Я уверен, что стандарт не является бесплатным, и поэтому это не окончательная / официальная версия.
1.10 многопоточные исполнения и гонки данных
- конфликтуют две оценки выражений, если одна из них изменяет память местоположение (1.7), а другой считывает или изменяет то же самое местоположение памяти.
- библиотека определяет ряд атомарных операций (пункт 29) и операций над мьютексами (пункт 30), которые специально определены как операции синхронизации. Эти операции играют особую роль в том, чтобы сделать назначения в одном потоке видимыми для другого. Операцию синхронизации на одной или более ячеек памяти либо потреблять операции, операции acquire, операция освобождение, или как приобретите и выпустите деятельность. Операции синхронизации без связанного памяти забор и можно либо приобрести забор, Забор релиз, или как получать и освобождать забор. Кроме того, существуют расслабленные атомарные операции, которые не являются операциями синхронизации, и атомарные операции чтения-изменения-записи, которые имеют специальные характеристики.
- два действия потенциально параллельны если
(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..
- должны быть частичные специализации указателя шаблона атомарного класса. Эти специализации должны иметь стандартную компоновку, тривиальные конструкторы по умолчанию и тривиальные деструкторы. Каждый из них должен поддерживать синтаксис инициализации aggregate.
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
изначально, нити T1
нагрузки 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 архитектуры руководство разработчика программного обеспечения раздел 8.2, начиная со страницы 2212.
примитивные типы (int
, char
etc) не являются атомными
потому что даже если они при определенных условиях могут иметь неделимые инструкции хранения и загрузки или, возможно, даже некоторые арифметические инструкции, они не гарантируют порядок магазинов и нагрузок. Как таковые они небезопасны для использования в многопоточных контекстах без надлежащей синхронизации с гарантируйте, что состояние памяти, наблюдаемое другими потоками, - это то, что вы думаете в этот момент времени.
надеюсь, это объясняет почему примитивные типы не являются атомарными.
дополнительная информация, которую я не видел в других ответах до сих пор:
если вы используете std::atomic<bool>
, например,bool
фактически является атомарным в целевой архитектуре, тогда компилятор не будет генерировать избыточные ограждения или блокировки. Тот же код будет сгенерирован, что и для plain bool
.
другими словами, с помощью std::atomic
только делает код менее эффективным, если он действительно требуется для корректности на платформе. Так что нет причин избегать он.