Русская Википедия:Блокировка чтения-записи

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Блокировка чтения-записи — механизм синхронизации, разрешающий одновременное общее чтение некоторых разделяемых данных либо их эксклюзивное изменение, разграничивая таким образом блокировки на чтение и на запись между собойШаблон:Sfn. Механизм призван решить классическую задачу о читателях-писателях, в которой некоторый объект одновременно читается и пишется конкурентными задачамиШаблон:Sfn.

В отличие от мьютексов блокировки чтения-записи отдельно учитывают чтение данных и отдельно — запись, разрешая обращение к данным, если они в это время не изменяются. Мьютексы же допускают только эксклюзивный доступ к даннымШаблон:Sfn. Однако встречаются разделяемые мьютексы, предоставляющие помимо эксклюзивной блокировки общую, позволяющую совместно владеть мьютексом, если нет эксклюзивного владельцаШаблон:Sfn. По своей сути разделяемые мьютексы являются блокировками чтения-записи, но именуются мьютексами.

В общем случае блокировки чтения-записи решают ту же задачу, что и мьютексы, и могут быть ими заменены, причиной же появления механизма блокировок чтения-записи является повышение эффективности взаимного исключения при раздельном чтении и записи[1]. Блокировки чтения-записи являются более предпочтительными, чем мьютексы, в случаях, когда обращения к данным происходят намного чаще, чем их запись. В этом случае читающие задачи не будут блокироваться большую части времени, лишь иногда блокируясь при изменениях объекта. Приоритет между пишущими и читающими задачами часто отдаётся пишущим задачам, чтобы избежать ресурсного голодания пишущих задачШаблон:Sfn.

Задача о читателях-писателях

Шаблон:Основная статья Проблема читателей и писателей возникает в любой ситуации, когда требуется одновременное чтение и изменение структуры данных, файловой системы или базы данных конкурентными задачами. Чтение неизменных данных может осуществляться одновременно множеством задач, однако если в это время будет происходить изменение данных, параллельное их чтение может привести к получению частично изменённых данных, то есть испорченныхШаблон:Sfn.

Решение задачи является асимметричным и предполагает разделение блокировки на чтение и на запись. Изменение данных допускается лишь эксклюзивное, то есть только одна задача может единовременно захватывать блокировку на запись, если только не захвачена блокировка на чтение. Чтение данных может осуществляться многими задачами, поэтому блокировку на чтение могут захватить одновременно сколько угодно задач, если только не захвачена блокировка на запись. То есть критические секции записи и чтения не могут исполняться параллельно, но критические секции чтения — могутШаблон:Sfn.

Алгоритмы реализации

Шаблон:См. также Самым простым алгоритмом реализации на семафорах и мьютексах является использование выключателя бинарного семафора. Запись должна защищаться данным семафором. Первая читающая задача должна захватывать семафор с помощью выключателя, блокируя пишущие потоки, а последняя заканчивающая свою работы — должна отпускать семафор, разрешая пишущим задачам продолжить свою работуШаблон:Sfn. Однако данная реализация имеет одну серьёзную проблему, сравнимую с взаимной блокировкой, — ресурсное голодание пишущих задачШаблон:Sfn.

Шаблон:Начало скрытого блока

Инициализация Читающая задача Пишущая задача
выключатель = Выключатель()
разрешение-записи = Семафор(1)
заблокировать(выключатель, разрешение-записи)
    // Критическая секция читающей задачи
разблокировать(выключатель, разрешение-записи)
захватить(разрешение-записи)
    // Критическая секция пишущей задачи
отпустить(разрешение-записи)

Шаблон:Конец скрытого блока

Универсальный алгоритм, лишённый описанной выше проблемы, включает в себя выключатель бинарного семафора А для организации критической секции читающих задач и турникет для блокировки новых читающих задач при наличии ожидающих пишущих. При появлении первой читающей задачи она захватывает семафор А с помощью выключателя, запрещая запись. Для пишущих задач семафор А защищает критическую секцию записи, поэтому, если он захвачен читающими задачами, все пишущие задачи блокируются при входе в свою критическую секцию. Однако захват пишущими задачами семафора А с последующей записью защищается семафором турникета. Поэтому, если произошла блокировка пишущей задачи из-за наличия читающих, турникет блокируется вместе с новыми читающими задачами. Как только последняя читающая заканчивает свою работу, семафор выключателя отпускается, и первая в очереди пишущая задача разблокируется. По окончании своей работы она отпускает семафор турникета, снова разрешая работу читающих задачШаблон:Sfn.

Шаблон:Начало скрытого блока

Инициализация Читающая задача Пишущая задача
выключатель = Выключатель()
разрешение-записи = Семафор(1)
турникет = Семафор(1)
захватить(турникет)
освободить(турникет)

заблокировать(выключатель, разрешение-записи)
    // Критическая секция читающей задачи
разблокировать(выключатель, разрешение-записи)
захватить(турникет)
    захватить(разрешение-записи)
    // Критическая секция пишущей задачи
отпустить(турникет)

отпустить(разрешение-записи)

Шаблон:Конец скрытого блока

На уровне операционных систем существуют реализации семафоров чтения и записи, которые специальным образом модифицируются для повышения эффективности при массовом использовании. Реализации блокировок чтения-записи могут быть основаны как на мьютексах, так и на спин-блокировках[1].

Проблемы использования

Хотя блокировки чтения-записи позволяют повысить скорость работы некоторых алгоритмов, им присуща скрытая проблема, которая проявляется при равномерной плотности запросов на чтение данных. В этом случае захват блокировки на запись может откладываться на неограниченные промежутки времени, порождая ресурсное голодание пишущих задач[1]. Ресурсное голодание пишущих задач сравнимо с взаимной блокировкой, поскольку запись данных будет невозможной, пока прибывают новые читающие задачи. При этом проблема может оказаться незаметной, пока нагрузка на систему не очень высокая, но может начать проявляться при возрастании нагрузки. Решение может быть заложено в реализацию блокировок чтения-записи и предполагает блокировку любых новых читающих задач, если в ожидании блокировки есть хотя бы одна пишущаяШаблон:Sfn.

Повышение блокировки до пишущей

Концепция повышения уровня блокировки позволяет повысить захваченную блокировку на чтение до эксклюзивной блокировки на запись. Повышение блокировки происходит, когда больше не оказывается других читающих задач, в противном случае задача блокируется до тех пор, пока читающие задачи не отпустят блокировку. Концепция также допускает понижение блокировки записи до блокировки чтения[2]. Однако концепция зачастую является опциональной и не обязана присутствовать в конкретных реализациях.

Прикладное программирование

Поддержка в POSIX

В стандарте POSIX блокировки чтения-записи представлены типом pthread_rwlock_t из заголовочного файла pthread.h. Блокировкам можно задавать некоторые параметры через атрибуты, в частности, блокировку можно определить как доступную между процессами или только между потоками, а обязательной по стандарту является блокировка, доступная между процессами. Если отсутствуют читающие задачи, порядок захвата блокировки пишущими задачами определяется выбранной политикой планировщика. Однако приоритет захвата блокировки между пишущими и читающими задачами по стандарту не определёнШаблон:Sfn.

Поддержка в Win32 API

В прикладном интерфейсе программирования Windows блокировки представлены структурой SRWLOCK из заголовочного файла Synchapi.h и набором функций для работы с ней. Блокировки предназначены для работы с потоками в рамках одного процесса, при этом не гарантируется какой-либо порядок захвата блокировок. Из особенностей поддерживается использование блокировки вместе с условной переменной через функцию SleepConditionVariableSRW()[3].

Поддержка в языках программирования

Блокировки чтения-записи в распространённых языках программирования
Язык Модуль или библиотека Тип данных
Си pthread pthread_rwlock_tШаблон:Sfn
C++ std std::shared_mutexШаблон:Sfn
C# System.Threading ReaderWriterLock[4]
Go sync RWMutex[5]
Java java.base, java.util.concurrent.locks ReentrantReadWriteLock[6]
Rust std std::sync::RwLock[7]

См. также

Примечания

Шаблон:Примечания

Литература