При длительном потоке читателей новые вызовы RLock начинают блокироваться, как только писатель ожидает Lock. Зачем sync.RWMutex устроен таким образом?
Это предотвращает голодание писателя: после появления ожидающего писателя новые читатели не получают блокировку, даже если текущие читатели ещё работают. Когда последние активные читатели выйдут, писатель сможет получить блокировку, выполнить критическую секцию и затем пропустить ожидающих читателей.
Обычный sync.Mutex не различает читателей и писателей: одновременно критическую секцию может выполнять только одна горутина. sync.RWMutex появился для сценариев, где чтений значительно больше, чем изменений, чтобы независимые читатели могли работать параллельно.
Такая оптимизация создаёт риск обратной стороны: если постоянно приходят новые читатели, писатель может бесконечно ждать. Поэтому механизм должен не только разрешать параллельные чтения, но и обеспечивать писателю возможность дождаться своей очереди.
Представим кэш конфигурации: множество запросов читает его, а отдельная горутина периодически обновляет данные. Если каждый новый читатель может сразу захватывать RLock, поток чтений способен постоянно поддерживать состояние «есть активные читатели».
В результате обновление будет задерживаться неопределённо долго. Это может привести к устаревшим данным, росту задержки обновления и накоплению ожидающих писателей.
Когда писатель вызывает Lock и не может сразу получить блокировку, sync.RWMutex начинает блокировать последующие вызовы RLock. Уже выполняющиеся читатели не прерываются: они завершают работу и вызывают RUnlock. После выхода последнего активного читателя ожидающий писатель получает возможность войти.
После освобождения писателем блокировки ожидающие читатели снова могут выполняться параллельно. Таким образом, приоритет не означает, что писатель прерывает читателей; он означает, что новые читатели не «перепрыгивают» через уже ожидающего писателя.
RLock следует использовать только для короткого защищённого чтения, а Lock — для операций, изменяющих состояние. Нельзя копировать используемый RWMutex; его обычно хранят как часть структуры и передают структуру по указателю.
RWMutex не гарантирует, что он будет быстрее Mutex. Он полезен при реальном преобладании чтений и достаточно долгих критических секциях. При частых записях, коротких операциях или высокой конкуренции дополнительные накладные расходы и переключения между режимами могут сделать обычный Mutex быстрее и проще.
В сервисе каталог товаров читается тысячами запросов в секунду и обновляется из административной системы несколько раз в минуту. Вариант с Mutex прост и предсказуем, но сериализует все чтения; вариант без блокировки может привести к гонке данных и неконсистентным наблюдениям.
RWMutex подходит, если чтение действительно доминирует и операции короткие. При этом обновление должно менять состояние под Lock, а чтение — полностью выполняться под RLock; нельзя снять блокировку, прочитать часть данных, а затем использовать их после изменения.
Выбранное решение — RWMutex плюс измерение задержек и конкуренции в нагрузочном тесте. Если обновления станут частыми или критическая секция окажется очень короткой, переход на Mutex может уменьшить сложность и улучшить производительность.
Нет. Читатели, уже успешно получившие RLock, продолжают выполнение. Блокируются только новые попытки чтения, чтобы ожидающий писатель дождался выхода текущих читателей.
RLock до Lock, не освобождая чтение?Нет. Попытка вызвать Lock, сохраняя собственный RLock, может привести к взаимной блокировке. Писатель ждёт завершения всех читателей, включая самого вызывающего; корректный шаблон — освободить RLock, затем отдельно получить Lock, понимая, что состояние за это время могло измениться.
RWMutex, что писатель всегда выполняется раньше всех читателей?Нет. Приоритет действует относительно новых читателей после появления ожидающего писателя. Уже активные читатели завершаются, а после освобождения писателем могут быть допущены ожидающие читатели; это не глобальная гарантия строгой очереди между всеми операциями.