При последовательных relaxed-загрузках одной атомарной переменной может ли второй результат относиться к более старой записи, чем первый?
Нет. Для одной атомарной переменной последующие загрузки в одном потоке не могут наблюдать записи в порядке, обратном порядку, уже наблюдённому этим потоком. Однако memory_order_relaxed не обеспечивает согласованность между разными атомиками и не делает обычные данные безопасными для конкурентного доступа.
Вторая загрузка может вернуть то же значение, что и первая, либо значение из более поздней записи в порядке модификаций. Она не может вернуться к более старой записи этой же атомарной переменной.
Модель памяти C++ появилась, чтобы формально описать многопоточность и одновременно допустить эффективные реализации на процессорах с различными правилами переупорядочивания. Атомики решают проблему конкурентного доступа к одной переменной без гонки данных.
Порядок relaxed предназначен для случаев, когда нужна атомарность и согласованность конкретного атомика, но не требуется публикация связанных обычных данных. Это позволяет компилятору и процессору избегать более дорогих ограничений упорядочивания.
Пусть один поток изменяет атомарный счётчик, а другой несколько раз читает его с memory_order_relaxed. Ошибочное рассуждение утверждает, что relaxed разрешает второй загрузке увидеть произвольное старое состояние, даже если первая загрузка уже увидела более новую запись.
Такое поведение нарушило бы согласованность одной атомарной переменной и сделало бы невозможными многие алгоритмы наблюдения за прогрессом. При этом relaxed действительно не гарантирует, что читатель немедленно увидит самую свежую запись или увидит согласованные значения нескольких разных атомиков.
У каждой атомарной переменной существует собственный порядок модификаций — единый порядок всех записей и атомарных RMW-операций над ней. Он согласован для всех потоков, хотя relaxed-загрузки не обязаны синхронизировать потоки.
Две загрузки, выполненные последовательно в одном потоке, связаны отношением sequenced-before, которое является частным случаем happens-before. Правило read-read coherence требует, чтобы вторая загрузка наблюдала ту же запись либо запись, расположенную позже первой в порядке модификаций. Поэтому возврат к более старой записи запрещён.
Это ограничение не означает монотонность числового значения само по себе. Если разные потоки записывают значения 100, затем 3, то наблюдатель может увидеть 100, затем 3: вторая запись новее, хотя число меньше. Гарантируется порядок наблюдённых записей, а не математическое сравнение их значений.
relaxed также не создаёт отношения synchronizes-with. Если поток записал обычное поле, а затем relaxed-записал флаг, relaxed-загрузка флага не делает чтение обычного поля безопасным. Для публикации данных обычно применяют release-запись и acquire-загрузку либо другой механизм синхронизации.
Минимальный пример использует возрастающие значения, поэтому запрет возврата к более старой записи выражается числовым сравнением:
Утверждение относится к двум загрузкам одного потока и возрастающим записям. Оно не утверждает, что читатель обязательно увидит 3, а также не распространяется на согласованность нескольких атомиков.
В сервисе поток обработки публикует возрастающий номер обработанного элемента, а поток мониторинга периодически читает этот номер. Для самого счётчика достаточно relaxed: монитор не должен наблюдать возврат к более старой записи, а публикация обычного состояния обработки через этот счётчик не требуется.
Вариант с мьютексом проще расширить на несколько связанных полей, но добавляет блокировки и риск задержек. seq_cst даёт более сильное глобальное упорядочивание атомарных операций, однако для одного независимого счётчика это избыточно. Выбран relaxed, потому что нужна только атомарность и согласованное наблюдение одной переменной; если монитор должен безопасно читать опубликованный объект, применяется release/acquire или мьютекс.
Нет. Она может увидеть не самую свежую запись с точки зрения реального времени. Гарантия касается невозможности наблюдать более старую запись после более новой в последовательных загрузках одного потока, а не мгновенной видимости последнего значения.
Причина в том, что relaxed не устанавливает межпоточное синхронизирующее отношение. Поток может продолжать наблюдать прежнее значение, если между его загрузками не произошло записи, которую он обязан увидеть по правилам модели памяти.
Нет. У каждой атомарной переменной свой порядок модификаций, а relaxed не создаёт единого глобального порядка операций над разными объектами.
Например, наблюдатель может увидеть обновление одного счётчика без обновления другого. Если алгоритму нужна согласованная публикация набора значений, используют release/acquire с корректным протоколом, мьютекс либо более сильное упорядочивание, если оно действительно требуется.
Нет. Атомарность счётчика защищает только саму атомарную переменную. Она не делает конкурентное чтение обычных полей безопасным и не гарантирует, что изменения этих полей станут видимыми в нужном порядке.
Для публикации используют release-операцию при публикации и acquire-операцию при наблюдении соответствующего значения. Тогда записи перед release становятся видимыми после успешного acquire, при условии что acquire наблюдает нужную release-последовательность; альтернативой является мьютекс.