Как следует рассуждать о переходе от совместной блокировки std::shared_mutex к исключительной, если условие проверено под shared lock?
У std::shared_mutex нет атомарного перехода от совместной блокировки к исключительной. Если поток отпускает shared lock, а затем запрашивает exclusive lock, между этими действиями другой поток может изменить защищаемое состояние, поэтому условие необходимо проверить повторно уже под исключительной блокировкой.
Блокировки читателей-писателей разделяют доступ на совместный для чтения и исключительный для записи. Это позволяет нескольким читателям работать параллельно, сохраняя единственного владельца при изменении данных.
Стандартный std::shared_mutex предоставляет независимые операции захвата и освобождения этих режимов, но не определяет операцию безопасного повышения режима. Такое ограничение явно отделяет простую блокировку от более сложных протоколов управления состоянием.
Типичный сценарий выглядит так: поток читает состояние под shared lock, обнаруживает необходимость изменения, освобождает совместную блокировку и пытается получить исключительную. В момент между освобождением и новым захватом другой поток может изменить данные или выполнить ту же операцию.
Если после получения исключительной блокировки использовать старый результат проверки, возникает логическая ошибка: изменение выполняется на основании уже неактуального состояния. Само отсутствие гонки данных при этом не гарантирует корректность алгоритма.
Безопасный общий протокол состоит в том, чтобы после получения исключительной блокировки заново проверить условие. Если условие больше не выполняется, поток должен отказаться от изменения; если оно всё ещё выполняется, изменение можно выполнить.
Такой переход не является атомарным: другой поток может получить исключительную блокировку первым. Однако повторная проверка под этой блокировкой сохраняет корректность.
Если операция изменения нужна почти всегда после проверки, выгоднее сразу использовать unique_lock: это проще и устраняет окно между режимами, но уменьшает параллелизм чтения. Если проверка дорогая, а изменение редкое, схема с повторной проверкой может быть эффективнее, однако она усложняет логику и иногда требует цикла повторных попыток.
Нельзя просто удерживать shared lock и одновременно пытаться получить exclusive lock: это может привести к взаимной блокировке, поскольку текущий поток сам препятствует получению исключительного режима.
В кэше несколько потоков совместно проверяют наличие записи. При отсутствии записи каждый пытается перейти к записи. Наивная схема с отпусканием shared lock приводит к тому, что несколько потоков одновременно увидят отсутствие записи и начнут конкурировать за инициализацию.
Вариант с немедленным unique_lock прост и надёжен, но блокирует чтение на время всей проверки. Вариант с отпусканием shared lock, последующим unique_lock и повторной проверкой сохраняет параллельность чтения, но только один поток выполнит инициализацию; остальные обнаружат, что запись уже появилась.
Для такого кэша обычно выбирают второй вариант, если проверка существенно чаще самой инициализации. Повторная проверка под исключительной блокировкой устраняет дублирование и сохраняет корректность без предположения о наличии атомарного повышения режима.
Нет. Нужно повторить все проверки, от которых зависит корректность изменения. Между первоначальным чтением и получением исключительной блокировки могли измениться не только наличие записи, но и версия, права доступа, лимиты или связанные поля.
try_lock способом безопасного повышения shared lock?Нет. try_lock лишь сообщает, удалось ли получить исключительную блокировку в конкретный момент. Он не делает переход атомарным и не сохраняет валидность результата, полученного под совместной блокировкой. После успешного try_lock состояние всё равно следует проверить заново.
Атомарность отдельного поля предотвращает гонку при обращении к нему, но не делает согласованной проверку нескольких полей и последующее изменение. Если решение зависит от составного состояния, нужна блокировка или специально спроектированный атомарный протокол, например сравнение и обмен единого состояния. Простая атомарность одного признака не превращает раздельные операции чтения и записи в одну неделимую транзакцию.