Найдите ошибку в рассуждении: после разблокировки мьютекса другой поток обязан увидеть изменения, сделанные до этой разблокировки, даже если изменённые данные не являются атомарными?
Да, обязан — при условии, что оба потока используют тот же мьютекс, а доступ к данным выполняется внутри соответствующих критических секций. Разблокировка мьютекса синхронизируется с последующей успешной блокировкой этого мьютекса и создаёт отношение happens-before, поэтому для таких данных атомарность не требуется.
Модель памяти C++ и правила синхронизации были формализованы, чтобы программа могла переносимо описывать взаимодействие потоков независимо от оптимизаций компилятора и особенностей процессора. Одной из базовых задач было безопасно работать с обычными объектами, не превращая каждое поле в атомарное.
Мьютекс решает эту задачу более высоким уровнем абстракции: он одновременно обеспечивает взаимное исключение и видимость изменений между потоками. Атомики предназначены для более узких операций над отдельными объектами и не заменяют автоматически согласованную защиту связанных данных.
Пусть один поток изменяет обычную переменную под мьютексом, затем освобождает его. Другой поток позже захватывает тот же мьютекс и читает переменную также под защитой.
Ошибочно считать, что обычная переменная обязательно создаёт гонку данных только потому, что к ней обращаются разные потоки. Риск возникает, если хотя бы один конфликтующий доступ выполняется без требуемой синхронизации или если потоки используют разные мьютексы.
При настоящей гонке данных поведение программы не определено. Возможны не только устаревшее значение или аппаратная несогласованность, но и оптимизации компилятора, которые делают рассуждения о порядке выполнения недействительными.
Операции, выполненные потоком до unlock, упорядочены перед последующей успешной операцией lock другого потока для того же мьютекса. Это даёт цепочку happens-before: запись данных происходит раньше разблокировки, разблокировка — раньше захвата, а захват — раньше чтения.
Поэтому защищённый обычный объект может быть согласованно прочитан без std::atomic. Мьютекс при этом должен быть один и тот же логически; совпадение защищаемых данных само по себе ничего не гарантирует.
value не атомарен, но программа корректна: reader захватывает тот же мьютекс после завершения writer. std::lock_guard гарантирует освобождение мьютекса при выходе из области видимости, в том числе при исключении.
Защита перестаёт быть достаточной, если чтение value вынести за пределы критической секции, использовать другой мьютекс или обращаться к объекту после его уничтожения. Также мьютекс не исправляет ошибки времени жизни: объект должен оставаться живым для всех потоков, которые его используют.
Атомарная переменная может быть дешевле для простого флага или счётчика, но атомарность одного поля не делает атомарным согласованное изменение нескольких полей. Мьютекс обычно предпочтительнее, когда инвариант включает несколько объектов или операция должна быть составной.
В сервисе поток обновляет конфигурацию из нескольких связанных полей: адрес, порт и режим работы. Другие потоки периодически читают всю конфигурацию.
Рассматривались два варианта:
Выбран мьютекс: обновление и чтение всей конфигурации выполняются под одной блокировкой. Это соответствует требованию согласованного снимка и устраняет гонку без искусственного усложнения управления временем жизни. Если профилирование покажет существенную конкуренцию чтения, следующим кандидатом может стать схема с неизменяемыми снимками и безопасной публикацией, а не независимые атомики для каждого поля.
Нет. Для обычного объекта конфликтующая запись и чтение должны быть упорядочены отношением happens-before. Если читатель не захватывает тот же мьютекс и не использует другой корректный механизм синхронизации, возникает гонка данных, даже если запись сама выполняется под блокировкой.
Нет. Гарантия относится к операциям, упорядоченным через конкретный мьютекс. Если поток изменил данные до разблокировки mutex_a, а другой поток захватил только mutex_b, причин для требуемой видимости нет, если между потоками отсутствует иной механизм синхронизации.
Только пока гарантировано время жизни объекта и соблюдаются правила дальнейшего доступа. Мьютекс может обеспечить видимость публикации указателя, но не предотвращает удаление объекта после выхода из критической секции. Для передачи владения обычно применяют подходящий механизм управления временем жизни, например копирование данных или совместное владение, а для чтения содержимого всё равно требуется отдельная корректная синхронизация.