Windows

       

Синхронизация в сценарии "один писатель/группа читателей"


Во многих приложениях возникает одна и та же проблемя синхронизации, о кото рой часто говорят как о сценарии «один писатель/группа читателей» (single-wrirer/ multiple-readers). В чем ее суть? Представьте: произвольное число потоков пытается

получить доступ к некоему разделяемому ресурсу. Каким-то потокам («писателям») нужно модифицировать данные, а каким-то («читателям») — лишь прочесть эти дан ные. Синхронизация такого процесса необходима хотя бы потому, что Вы должны соблюдать следующие правила:

  • Когда один поток что-то пишет в область общих данных, другие этого делать не могут.

  • Когда один поток что-то пишет в область общих данных, другие не могут ни чего считывать оттуда.

  • Когда один поток считывает что-то из области общих данных, другие не мо гут туда ничего записывать

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

    В этом сценарии правило 1 необходимо потому, что мы, конечно же, не можем позволить одновременно обновлять одну и ту же запись. Иначе информация в запи си будет повреждена.

    Правило 2 запрещает доступ к записи, обновляемой в данный момент другим пользователем Будь то иначе, один пользователь считывал бы запись, когда другой пользователь изменял бы ее содержимое Что увидел бы на мониторе своего компью тера первый пользователь, предсказать не берусь. Правило 3 служит тем же целям, что и правило 2. И действительно, какая разница, кто первый получит доступ к данным: тот, кто записывает, или тот, кто считывает, — все равно одновременно этого делать нельзя

    И, наконец, последнее правило. Оно введено для большей эффективности работы баз данных. Если никто не модифицирует записи в базе данных, все пользователи могут свободно читать любые записи Также предполагается, что количество «читате лей" превышает число «писателей».

    О'кэй, суть проблемы Вы ухватили. А теперь вопрос: как ее решить?




    NOTE:
    Я представлю здесь совершенно новый код Решения этой проблемы, которые я публиковал в прежних изданиях, часто критиковались по двум причинам. Во первых, предыдущие реализации работали слишком медленно, так как я пи сал их в расчете на самые разные сценарии Например, я шире использовал объекты ядра, стремясь синхронизировать доступ к базе данных потоков из разных процессов. Конечно, эти реализации работали и в сценарии для одно го процесса, но интенсивное использование объектов ядра приводило в этом случае к существенным издержкам. Похоже, сценарий для одного процесса более распространен, чем я думал

    Во-вторых, в моей реализации был потенциальный риск блокировки no токов-«писателей» Из правил, о которых я рассказал в начале этого раздела, вытекает, что потоки-«писатели» — при обращении к базе данных очень боль шого количества потоков-«читателей» — могут вообще не получить доступ к этому ресурсу



    Все эти недостатки я теперь устранил. В новой реализации объекты ядра применяются лишь в тсх случаях, когда без них нс обойтись, и потоки синх ронизируются в основном за счет использования критической секции.

    Плоды своих трудов я инкапсулировал в С++-класс CSWMRG (я произношу его название как swimerge); это аббревиатура от «single writer/multiple reader guard». Он содержится в фцйлах SWMRG.h и SWMRG.cpp (см. листинг на рис. 10-3).

    Использовать CSWMRG проще простого. Вы создаете объект С++-класса CSWMRG и вызываете нужные в Вашей программе функции-члены. В этом классе всего три метода (не считая конструктора и деструктора);

    VOID CSWMRG:;WaitToRead(); // доступ к разделяемому ресурсу для чтения

    VOID CSWMRG::WaitToWrite(); // монопольный доступ к разделяемому ресурсу для записи

    VOID CSWMRG::Done(); // вызывается по окончании работы с ресурсом

    Первый метод (WaitToRead) вызывается перед выполнением кода, что-либо считы вающего из разделяемого ресурса, а второй (WaitToWrite) — перед выполнением кода, который считывает и записывает данные в разделяемом ресурсе. К последнему мето ду (Done) программа обращается, закончив работу с этим ресурсом. Куда уж проще, а?



    Объект CSWMRG содержит набор переменных-членов (см. таблицу ниже), отра жающих то, как потоки работают с разделяемым ресурсом на данный момент. Осталь ные подробности Вы узнаете из исходного кода.

    Переменная Описание
    m_cs Охраняет доступ к остальным членам класса, обеспечивая опера ции с ними на атомарном уровне
    т_nActive Отражает текущее состояние разделяемого ресурса. Если она равна 0, ни один поток к ресурсу не обращается. Ее значение, большее 0, со общает текущее число потоков, считывающих данные из ресурса. Отрицательное значение (-1) свидетельствует о том, что какой-то поток записывает данные в ресурс.
    m_nWaitingReaders Сообщает количество потоков «читателей», которым нужен доступ к ресурсу. Значение этой переменной инициализируется 0 и увели чивается на 1 всякий раз, когда поток вызывает WaitToRead в то вре мя, как т nActive равна — 1.
    т_nWaitingWriters Сообщает количество потоков-«писателей», которым нужен доступ к ресурсу. Значение этой переменной инициализируется 0 и увели чивается на 1 всякий раз, когда поток вызывает WaitToWrite в то вре мя, как т nActive больше 0,
    т_hsemWriters Когда потоки - " писатели"> вызывают WaitToWrtie, но получают отказ в доступе, так как m_nActive больше 0, они переходят в состояние ожидания этого семафора. Пока ждет хотя бы один поток-«писа тель», новые потоки-«читатели» получают отказ в доступе к ресурсу. Тем самым я не даю потокам- «читателям" монополизировать доступ к этому ресурсу Когда последний поток-«читатсль», работавший с ресурсом, вызывает Done, семафор освобождается со счетчиком, равным 1, и система пробуждает один ждущий поток-описатель».
    m_hsemReaders Когда потоки-«читатели" вызывают' WaitToRead, но получают отказ в доступе, так как т nActive равна - 1, они переходят в состояние ожидания этого семафора. Когда последний из ждущих потоков «писателей» вызывает Done, семафор освобождается со счетчиком, равным т nWaitingReaders, и система пробуждает все ждущие пото ки -«читатели».

    Содержание раздела