Во-первых, volatile
не подразумевает атомарный доступ. Он предназначен для таких вещей, как ввод-вывод с отображением в память и обработка сигналов. volatile
совершенно не требуется при использовании с std::atomic
, и если ваша платформа не документирует иначе, volatile
не имеет отношения к атомарному доступу или упорядочению памяти между потоками.
Если у вас есть глобальная переменная, которая используется несколькими потоками, например:
std::atomic<int> ai;
тогда ограничения видимости и упорядочения зависят от параметра упорядочения памяти, который вы используете для операций, и эффектов синхронизации блокировок, потоков и обращений к другим элементарным переменным.
В отсутствие какой-либо дополнительной синхронизации, если один поток записывает значение в ai
, то нет ничего, что гарантировало бы, что другой поток увидит значение в любой заданный период времени. Стандарт определяет, что он должен быть виден «в разумный период времени», но любой данный доступ может вернуть устаревшее значение.
Порядок памяти по умолчанию std::memory_order_seq_cst
обеспечивает единый глобальный общий порядок для всех операций std::memory_order_seq_cst
по всем переменным. Это не означает, что вы не можете получить устаревшие значения, но это означает, что полученное вами значение определяет и определяется тем, где в этом общем порядке находится ваша операция.
Если у вас есть 2 общие переменные x
и y
, изначально равные нулю, и один поток записывает 1 в x
, а другой записывает 2 в y
, то третий поток, который читает оба, может увидеть либо ( 0,0), (1,0), (0,2) или (1,2), поскольку между операциями нет ограничений на порядок и, следовательно, операции могут появляться в любом порядке в глобальном порядке.
Если обе записи производятся из одного потока, который выполняет x=1
до y=2
и поток чтения читает y
до x
, то (0,2) больше не является допустимым параметром, так как чтение y==2
подразумевает, что более ранняя запись в x
является видимой. Другие 3 пары (0,0), (1,0) и (1,2) все еще возможны, в зависимости от того, как чтение 2 перемежается с записью 2.
Если вы используете другие упорядочения памяти, такие как std::memory_order_relaxed
или std::memory_order_acquire
, тогда ограничения еще более ослабляются, и единый глобальный упорядочивание больше не применяется. Потоки даже не обязательно должны согласовывать порядок двух хранилищ для разделения переменных, если нет дополнительной синхронизации.
Единственный способ гарантировать, что у вас есть «последнее» значение, это использовать операцию чтения-изменения-записи, такую как exchange()
, compare_exchange_strong()
или fetch_add()
. Операции чтения-изменения-записи имеют дополнительное ограничение, заключающееся в том, что они всегда работают с «последним» значением, поэтому последовательность операций ai.fetch_add(1)
с помощью ряда потоков будет возвращать последовательность значений без дубликатов или пробелов. При отсутствии дополнительных ограничений, все еще нет гарантии, какие потоки будут видеть, какие значения.
Работа с атомарными операциями - сложная тема. Я предлагаю вам прочитать много справочных материалов и изучить опубликованный код перед тем, как писать производственный код с использованием атомарного кода. В большинстве случаев проще написать код, использующий блокировки, и не менее заметно эффективный.