Если std::mutex::try_lock завершился неудачей, можно ли считать, что поток получил актуальное состояние защищаемых данных?
Нет. Неудачный try_lock не захватывает мьютекс и не создаёт отношения synchronizes-with с предыдущей разблокировкой. Поэтому поток не получает гарантии видимости защищаемых данных и не должен читать их как будто мьютекс был захвачен.
Блокирующий lock удобен, когда поток обязан дождаться доступа, но иногда ожидание нежелательно: например, в цикле обработки событий или при попытке выполнить необязательное обновление. try_lock появился как неблокирующий способ проверить возможность немедленно войти в критическую секцию.
Однако его назначение — сообщить, удалось ли получить владение мьютексом, а не предоставить безопасный способ наблюдать состояние защищённых данных.
Поток может получить false из try_lock, пока другой поток владеет мьютексом и изменяет обычные переменные. Если после этого прочитать такие переменные без другого механизма синхронизации, возникнет гонка данных, а поведение программы станет неопределённым.
Даже если на конкретной архитектуре чтение выглядит корректным, стандарт C++ не разрешает делать выводы о видимости или согласованности данных по одному факту неудачной попытки захвата.
Только успешный захват мьютекса устанавливает необходимые гарантии: операции, выполненные до разблокировки мьютекса одним потоком, становятся видимыми после успешного захвата этого мьютекса другим потоком. Неудачный try_lock не даёт владения, поэтому таких гарантий нет.
Если try_lock вернул true, поток обязан соблюдать обычные правила владения: выполнить чтение или запись внутри критической секции и освободить мьютекс. Если возвращено false, допустимы только действия, не требующие доступа к защищённому состоянию, либо чтение данных, синхронизированных отдельно.
Для необязательного чтения можно использовать атомарный снимок, неизменяемый объект с безопасной публикацией или другой самостоятельный протокол синхронизации. Нельзя трактовать try_lock как операцию acquire независимо от результата: acquire-семантика относится к успешному захвату.
Поток интерфейса пытается быстро получить состояние кэша, который обновляет фоновый поток. Возможные варианты — всегда блокироваться на мьютексе, пропускать обновление или читать кэш после неудачного try_lock.
Блокировка даёт простую и строгую семантику, но может задержать интерфейс. Пропуск безопасен, если интерфейс умеет использовать ранее полученный снимок. Чтение при неудачном захвате небезопасно для обычных полей, даже если обновление обычно происходит редко.
Практичное решение — при успешном захвате быстро скопировать состояние и освободить мьютекс, а при неудаче использовать уже имеющийся самостоятельно синхронизированный снимок или отложить операцию. Это сохраняет корректность и ограничивает время ожидания.
Создаёт ли неудачный try_lock хотя бы acquire-барьер?
Нет. Неудачный результат означает, что мьютекс не был захвачен. Следовательно, он не может синхронизироваться с разблокировкой другого потока и не гарантирует видимость его записей.
Безопасно ли читать защищённое поле после try_lock, если другой поток уже отпустил мьютекс?
Нет гарантии. Поток не знает, почему попытка завершилась неудачей с точки зрения протокола доступа, а сам неудачный вызов не предоставляет нужного отношения happens-before. Для обычного поля чтение должно выполняться после успешного захвата либо через отдельный механизм синхронизации.
Можно ли после неудачного try_lock читать атомарную копию состояния?
Да, если эта копия действительно поддерживает собственный корректный протокол доступа. Например, атомарная переменная может безопасно предоставить отдельное значение, но это не превращает остальные поля объекта в безопасно читаемые и не гарантирует, что набор нескольких атомиков образует согласованный снимок.