И наоборот, если операция не атомарная, то другой поток может видеть, что она выполнена частично. Если это операция сохранения, то значение, наблюдаемое другим потоком, может не совпадать ни со значением до начала сохранения, ни с сохраненным значением. С другой стороны, операция загрузки может извлечь часть объекта, после чего значение будет модифицировано другим потоком, а затем операция прочитает оставшуюся часть объекта. В результате будет извлечено значение, которое объект не имел ни до, ни после модификации. Это простая проблематичная гонка, описанная в главе 3, но на этом уровне она может представлять собой гонку за данными (см. раздел 5.1) и, стало быть, являться причиной неопределённого поведения.
В С++ для того чтобы операция была атомарной, обычно необходимы атомарные типы. Давайте познакомимся с ними.
5.2.1. Стандартные атомарные типы
Все стандартные атомарные типы определены в заголовке . Любые операции над такими типами атомарны, и только операции над этими типами атомарны в смысле принятого в языке определения, хотя мьютексы позволяют реализовать кажущуюся атомарность других операций. На самом деле, и сами стандартные атомарные типы могут пользоваться такой эмуляцией: почти во всех имеется функция-член is_lock_free()
, которая позволяет пользователю узнать, выполняются ли операции над данным типом с помощью действительно атомарных команд ( x.is_lock_free()
возвращает true
) или с применением некоторой внутренней для компилятора и библиотеки блокировки ( x.is_lock_free()
возвращает false
).
Единственный тип, в котором функция-член is_lock_free()
отсутствует, — это std::atomic_flag
. В действительности это по-настоящему простой булевский флаг, а операции над этим типом обязаны быть свободными от блокировок; если имеется простой свободный от блокировок булевский флаг, то на его основе можно реализовать простую блокировку и, значит, все остальные атомарные типы. Говоря по-настоящему простой , я именно это и имел в виду: после инициализации объект типа std::atomic_flag
сброшен, и для него определены всего две операции: проверить и установить (функция-член test_and_set()
) и очистить (функция-член clear()
). Это всё — нет ни присваивания, ни копирующего конструктора, ни операции «проверить и очистить», вообще ничего больше.
Доступ ко всем остальным атомарным типам производится с помощью специализаций шаблона класса std::atomic<>
; их функциональность несколько богаче, но они необязательно свободны от блокировок (как было объяснено выше). На самых распространенных платформах можно ожидать, что атомарные варианты всех встроенных типов (например, std::atomic
и std::atomic
) действительно будут свободны от блокировок, но такого требования не предъявляется. Как мы скоро увидим, интерфейс каждой специализации отражает свойства типа; например, поразрядные операции, например &=
, не определены для простых указателей, поэтому они не определены и для атомарных указателей.
Помимо прямого использования шаблона класса std::atomic<>
, разрешается использовать имена, приведённые в табл. 5.1, которые ссылаются на определенные в конкретной реализации атомарные типы. Из-за исторических особенностей добавления атомарных типов в стандарт С++ альтернативные имена типов могут ссылаться либо на соответствующую специализацию std::atomic<>
, либо на базовый класс этой специализации. Поэтому смешение альтернативных имен и прямых имен специализаций std::atomic<>
может сделать программу непереносимой.
Таблица 5.1.Альтернативные имена стандартных атомарных типов и соответствующие им специализации std::atomic<>
Атомарный тип |
Соответствующая специализация |
atomic_bool |
std::atomic |
atomic_char |
std::atomic |
atomic_schar |
std::atomic |
atomic_uhar |
std::atomic |
atomic_int |
std::atomic |
atomic_uint |
std::atomic |
atomic_short |
std::atomic |
atomic_ushort |
std::atomic |
atomic_long |
std::atomic |
atomic_ulong |
std::atomic |
atomic_llong |
std::atomic |
atomic_ullong |
std::atomic |
atomic_char16_t |
std::atomic |
atomic_char32_t |
std::atomic |
atomic_wchar_t |
std::atomic |
Помимо основных атомарных типов, в стандартной библиотеке С++ определены также псевдонимы typedef
для атомарных типов, соответствующих различным неатомарным библиотечным typedef
, например std::size_t
. Они перечислены в табл. 5.2.
Читать дальше