Блокировка чтения-записи
Блокировка чтения-записи (англ. readers–writer lock, shared-exclusive lock) — это механизм синхронизации доступа к общему ресурсу в параллельных вычислениях, который позволяет нескольким потокам одновременно читать данные, но предоставляет эксклюзивный доступ только одному потоку при записи. Данный подход повышает производительность в сценариях, где операции чтения значительно преобладают над операциями записи, за счёт снижения конкуренции между потоками.
Основные принципы работы
Блокировка чтения-записи основана на разделении доступа на два режима:
- Режим чтения (shared lock) — любое количество потоков может одновременно удерживать блокировку в этом режиме. Потоки, выполняющие чтение, не мешают друг другу, так как не изменяют данные.
- Режим записи (exclusive lock) — только один поток может удерживать блокировку в этом режиме. Во время записи никакие другие потоки не могут ни читать, ни записывать данные.
Переключение между режимами происходит следующим образом: если ни один поток не удерживает блокировку, запрос на чтение или запись удовлетворяется немедленно. Если блокировка удерживается в режиме чтения, новый запрос на чтение также удовлетворяется, а запрос на запись ставится в очередь. Если блокировка удерживается в режиме записи, все последующие запросы (как на чтение, так и на запись) блокируются до её освобождения.
Классификация
По приоритету доступа
Существует несколько стратегий обработки конкуренции между читателями и писателями:
- Читатели с приоритетом (readers-preference) — если блокировка удерживается читателями, новые читатели могут получить доступ, даже если в очереди ожидает писатель. Это может привести к «голоданию» писателей, когда они никогда не получат доступа из-за непрерывного потока читателей.
- Писатели с приоритетом (writers-preference) — как только писатель заявляет о намерении записать данные, новые читатели не допускаются до тех пор, пока писатель не завершит работу. Это предотвращает голодание писателей, но может снизить параллелизм чтения.
- Справедливая блокировка (fair lock) — запросы обрабатываются в порядке очереди (FIFO). Ни читатели, ни писатели не имеют преимущества, что обеспечивает равномерный доступ, но может снизить общую пропускную способность.
По реализации
- Блокирующие (blocking) — поток, не получивший доступ, переводится в состояние ожидания (sleep) до освобождения ресурса. Требуют поддержки со стороны операционной системы.
- Спин-блокировки (spinlock) — поток активно проверяет состояние блокировки в цикле, не переходя в режим ожидания. Эффективны при коротких операциях, но потребляют процессорное время.
- Блокировки с тайм-аутом — поток ожидает доступ в течение заданного времени, после чего возвращает ошибку. Используются для предотвращения взаимных блокировок (deadlock).
Реализация
Программные интерфейсы
В большинстве языков программирования и операционных систем существуют встроенные реализации блокировок чтения-записи:
- POSIX Threads (pthreads) —
pthread_rwlock_tв Linux и других Unix-подобных системах. Поддерживает операцииpthread_rwlock_rdlock,pthread_rwlock_wrlockиpthread_rwlock_unlock. - Windows API —
SRWLOCK(Slim Reader/Writer Lock) в Windows Vista и более новых версиях. Используется с функциямиAcquireSRWLockShared,AcquireSRWLockExclusiveиReleaseSRWLock. - Java —
ReentrantReadWriteLockиз пакетаjava.util.concurrent.locks. Позволяет настраивать справедливость и поддерживает реентерабельность (повторный захват блокировки тем же потоком). - C++ —
std::shared_mutex(начиная с C++17) иstd::shared_lockдля чтения,std::lock_guardдля записи. - Python —
threading.RLockне поддерживает разделение; для блокировок чтения-записи используются сторонние библиотеки, напримерreaderwriterlock.
Пример реализации на C++
```cpp
include <shared_mutex>
include <thread>
include <vector>
std::shared_mutex rw_mutex; int shared_data = 0;
void reader() { std::shared_lock lock(rw_mutex); // Чтение shared_data }
void writer() { std::unique_lock lock(rw_mutex); // Запись в shared_data } ```
Применение
Блокировки чтения-записи широко используются в следующих областях:
- Базы данных — системы управления базами данных (СУБД) применяют этот механизм для управления одновременным доступом к таблицам и строкам. Например, в PostgreSQL используется блокировка на уровне строк, где читатели не блокируют других читателей.
- Файловые системы — операционные системы используют блокировки чтения-записи для синхронизации доступа к файлам. В Linux системный вызов
flockподдерживает раздельные блокировки на чтение и запись. - Кэширование — в многопоточных кэшах (например, в веб-серверах) чтение данных из кэша происходит часто, а запись — редко. Блокировка чтения-записи позволяет обслуживать множество запросов параллельно.
- Редакторы кода и текстовые процессоры — при совместной работе над документами несколько пользователей могут одновременно читать файл, но только один — вносить изменения.
- Системы реального времени — в авионике и промышленных контроллерах, где требуется детерминированное поведение, используются блокировки с приоритетом писателей для гарантии актуальности данных.
Преимущества и недостатки
Преимущества
- Повышенная производительность — при высокой доле операций чтения (более 80–90%) блокировка чтения-записи значительно эффективнее обычной взаимной блокировки (mutex), так как позволяет параллельное чтение.
- Гибкость — возможность настройки приоритета позволяет адаптировать механизм под конкретные сценарии использования.
- Предотвращение гонок данных — гарантирует, что ни один поток не прочитает данные в момент их изменения.
Недостатки
- Сложность реализации — неправильная обработка приоритетов может привести к голоданию (starvation) или взаимным блокировкам (deadlock).
- Накладные расходы — управление очередями и переключение контекста требуют дополнительных ресурсов по сравнению с простым mutex.
- Неэффективность при коротких операциях — если время чтения или записи мало, накладные расходы на блокировку могут превысить выгоду от параллелизма.
- Проблема «читательского голодания» — при приоритете читателей писатели могут никогда не получить доступ, если читатели постоянно активны.
Альтернативы
- Взаимная блокировка (mutex) — простейший механизм, предоставляющий эксклюзивный доступ. Подходит для сценариев с равной долей чтения и записи.
- Блокировка без ожидания (lock-free) — алгоритмы, использующие атомарные операции (CAS — compare-and-swap) для синхронизации без блокировок. Применяются в высоконагруженных системах, где блокировки недопустимы.
- Транзакционная память (Transactional Memory) — аппаратная или программная поддержка атомарных блоков кода, упрощающая написание параллельных программ.
- Семафоры — более общий механизм синхронизации, который может эмулировать блокировку чтения-записи, но требует ручного управления.
Интересные факты
- Первое описание блокировки чтения-записи в научной литературе датируется 1971 годом в работе «Concurrent Control with Readers and Writers» Питера Дж. Куртона (Peter J. Courtois) и его коллег.
- В операционной системе Linux реализация
pthread_rwlock_tиспользует атомарные операции и спин-блокировки для коротких ожиданий, что повышает производительность на многоядерных процессорах. - В языке программирования Go блокировка чтения-записи реализована через
sync.RWMutex, который автоматически предотвращает голодание писателей, отдавая им приоритет после определённого времени ожидания.
Источники
- Courtois, P. J., Heymans, F., & Parnas, D. L. (1971). «Concurrent Control with Readers and Writers». Communications of the ACM.
- Herlihy, M., & Shavit, N. (2012). «The Art of Multiprocessor Programming». Morgan Kaufmann.
- Документация POSIX.1-2017:
pthread_rwlock_rdlock,pthread_rwlock_wrlock. - Документация Microsoft Windows: «Slim Reader/Writer (SRW) Locks».
- Документация Java: «ReentrantReadWriteLock» (Oracle).
BFOmetr — база данных и аналитика по компаниям России.
На главную BFOmetr →