Thursday, August 26, 2010

Класс для организации многопоточноого доступа "multiple read, one write"

Несколько дней усердно работал над созданием класса, обеспечивающего доступ "multiple read, one write" (wiki). Конечно же, я искал в интернете знания других людей по решению этой задачи, и задача эта решена уже много раз, но варианта для PHP я не нашёл.

Задача

Читатели не блокируют ресурс и могут читать одновременно все, а вот писатели пишут по очереди. Кроме того, когда писатели используют ресурс, читатели не читают, а ожидают завершения действия писателей. Писатели могут начинать писать только тогда, когда нет читающих.

Решение

Решение создавалось непривычно долго. Кроме того, большая часть работы являлась логической, то есть я просто думал и рисовал - строчек кода не появлялось долгое время. Дело в том, что в PHP есть семафоры для создания мьютексов, но нет возможности атомарного (и эксклюзивного) инкремента/декремента значения в оперативной памяти (счётчика).

Для чего это нужно: читатели не должны блокировать друг друга, значит они не могут пользоваться мьютексом (он выстроит их в очередь), но писатели должны знать о существовании читателей (одновременно с ними читающих ресурс), и читатели должны знать о том, что писатели появились и нужно их подождать.


Единственным вариантом здесь является использование счётчика - читатели увеличивают счётчик читателей в начале чтения и уменьшают после прочтения. Писатели увеличивают/уменьшают счётчик писателей. Счётчики видны всем - тогда читатели перед чтением будут обязаны подождать, пока счётчик писателей станет равен нулю; писатели перед записью смогут подождать, пока счётчик читателей станет равен нулю.

И тут есть две загвоздки:
* В PHP нет атомарного счётчика. Два процесса могут одновременно прочитать из памяти значение (0), успешно прибавить к нему единицу (0+1) и оба впишут в память получившийся результат (1), который на самом деле должен быть на единицу больше.
* Писатели обязаны пользоваться мьютексом, чтобы получать доступ к записи по очереди. Если использовать только счётчики - несколько писателей могут одновременно прочитать в счётчиках 0 и приступят к записи... одновременно.

Первая загвоздка решилась использованием очередей сообщений (msg_*) - количество сообщений в очереди стало счётчиком, процесс отправлял сообщение, увеличивая счётчик, в начале, и принимал своё же сообщение, уменьшая счётчик, в конце.
Ну а вторая загвоздка - просто подводный камень, который был обнаружен не сразу, а исправлен был моментально.

Алгоритм

Получение доступа для чтения:
* Проверить, что нет писателей. Если есть - ждать, пока счётчик писателей не будет равен нулю.
* Увеличить счётчик читателей.
После чтения:
* Уменьшить счётчик читателей.

Получение доступа для записи:
* Захватить эксклюзивно семафор писателей.
* Увеличить счётчик писателей.
После записи:
* Уменьшить счётчик писателей.
* Вернуть семафор писателей.

Код PHP

Копия скрипта на сайте pastebin
(js-скрипт подсветки кода для blogspot создаёт отсебятину в коде).
Код проверен на рабочем сервере (именно при многопоточном использовании).
Используйте как захочется :) Советы по улучшению и конструктивная критика принимаются.

No comments:

Post a Comment