После замены std::mutex на std::shared_mutex почему запись может начать голодать?
std::shared_mutex допускает одновременное владение несколькими читателями, но не гарантирует справедливое чередование читателей и писателей. Если новые читатели продолжают получать shared-владение, ожидающий писатель может долго не получить эксклюзивный доступ. Поэтому такая замена способна ухудшить задержку записи, несмотря на рост параллелизма чтения.
Обычный std::mutex сериализует всех участников: пока один поток владеет мьютексом, остальные ждут. Для структур с большим числом операций чтения это может быть избыточным ограничением, поскольку независимые читатели не обязаны блокировать друг друга.
std::shared_mutex появился как средство разделить два режима доступа: совместный для чтения и эксклюзивный для изменения. Такой подход полезен в сценариях «много чтений — мало записей», где стоимость синхронизации оправдывается одновременной работой читателей.
Писатель должен дождаться завершения всех текущих читателей. Если реализация разрешает новым читателям входить в критическую секцию, пока писатель уже ожидает, поток читателей может постоянно поддерживать занятое shared-состояние.
Стандарт C++ не устанавливает универсальную гарантию отсутствия голодания или конкретную политику приоритета писателей. Поэтому программа, которой нужна ограниченная задержка записи, не должна полагаться на предположение о справедливости конкретной реализации.
У std::shared_mutex есть два логических режима владения:
Писатель блокируется не только текущими читателями, но и потенциально последующими попытками чтения. Точное поведение при наличии ожидающего писателя зависит от реализации, однако переносимость гарантии «писатель обязательно скоро пройдёт» отсутствует.
Следует различать две причины ухудшения производительности. Первая — голодание писателя из-за политики допуска читателей. Вторая — обычные накладные расходы shared-lock: учёт владельцев, конкуренция за внутреннее состояние блокировки и стоимость синхронизации могут превысить выигрыш, если критическая секция короткая или читателей немного.
Практический выбор зависит от требований:
Блокировка защищает данные только пока поток действительно удерживает соответствующий объект владения. Сам факт использования std::shared_mutex не делает данные атомарными и не разрешает запись под shared-владением.
В кэше конфигурации чтения происходят тысячи раз чаще изменений. Команда заменила std::mutex на std::shared_mutex и получила более высокую пропускную способность чтения, но обновление конфигурации стало иногда задерживаться на сотни миллисекунд из-за непрерывного потока читателей.
Рассматривались три варианта. Возврат к std::mutex упростил бы поведение и сделал задержку более предсказуемой, но снизил бы параллелизм чтения. Оставление std::shared_mutex без изменений сохранило пропускную способность, но не решило проблему ожидания записи. Добавление политики приоритета ожидающего писателя ограничило бы голодание, но увеличило бы сложность синхронизации.
Если критична именно гарантированная задержка обновления, разумным выбором может быть обычный std::mutex при короткой критической секции. Если же чтения тяжёлые, записи редки, а задержка обновления допускает некоторую вариативность, std::shared_mutex следует оставить после измерений и проверки поведения целевой стандартной библиотеки.
Нет. Стандарт задаёт допустимые режимы владения, но не требует конкретной политики справедливости между ожидающими читателями и писателями. Реализация может предотвращать бесконечный поток новых читателей, однако переносимый код не должен считать это гарантированным свойством. При жёстких требованиях к задержке нужна собственная политика или другой примитив.
Нет. Shared-владение совместимо с другим shared-владением, но exclusive-владение несовместимо с любым другим владением. Писатель получает доступ только после выхода всех читателей, а новые читатели не могут войти до завершения exclusive-владения.
Параллелизм полезен только тогда, когда стоимость одновременной работы читателей превышает стоимость управления блокировкой. При коротких критических секциях потоки могут тратить больше времени на координацию, изменение счётчиков владельцев и синхронизацию кэш-линий, чем на сам доступ к данным.
Кроме того, производительность ограничивается не только блокировкой: читатели могут конкурировать за кэш, память или другие ресурсы. Поэтому выбор std::shared_mutex следует подтверждать нагрузочными измерениями, а не делать по одному признаку «операций чтения больше, чем записей».