Не исключительная блокировка Reader_Writer_Locks

Phoenix78

Client
Read only
Регистрация
06.11.2018
Сообщения
11 790
Благодарностей
5 683
Баллы
113
Удивительно, но поиск по тэгу ReaderWriterLockSlim, по форуму зенки не нашел ни одного поста с применением этого функционала. Надо закрыть этот пробел ;-)
Итак подробно про ReaderWriterLockSlim читаем тут
В вкратце суть, обычный лок создает исключительную блокировку кода, а если работаем через глобальную переменную, то любой шаблон который использует эту блокировку будет простаивать в этом месте.
А так как есть операции с ресурсами которые занимают сотни миллисикунд, то уже сотня потоков запросто уходит в паузу на десятки секунд.
В определенных ситуациях это необходимые жертвы, например запись в файл, так как без блокировки у нас получится битый/невалидный набор данных в конкурирующем ресурсе.
Однако очень много ситуаций когда надо просто прочитать данные из объекта, что не является критической ситуацией для работы в многопотоке и читать безопасно могут сразу сотни потоков. И в этой ситуации нам надо иметь блокировку которая не будет блокировать чтение из ресурса, но при попытке записи в разделяемый ресурс она должна сработать как исключительная и выдать разрешение только одному потоку.
И вот класс ReaderWriterLockSlim как раз все это реализует. У него даже есть режим апгрейда блокировки чтения в блокировку записи прямо на лету, что очень удобно когда поиск в разделяемом ресурсе может затянуться и что бы не блокировать работу всех потоков на время поиска, сначала ставиться блокировка на чтение, а когда наступает время внести изменения в объект, то она меняется на режим записи и после выполнения операции сразу же меняется на чтение.
Все это можно посмотреть в примере по ссылке.

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

// неймспейс и имя новой переменой для синхронизации
string namespaceName = "test_namespace";
string globVarName = "my_sync_object2";

// Выполняется в начале проекта
lock (project.GlobalVariables)
{
    // проверка на существование глобальной переменной
    try {
        // попытка получения объекта (переход на catch при неудаче)
        var syncobj = project.GlobalVariables[namespaceName, globVarName];
        return syncobj.ToString(); // возврат его значения (отобразится в логе PM, если возможно)
    } catch (KeyNotFoundException ex) {
        // создание объекта синхронизации и его установка
        object syncobj = new ReaderWriterLockSlim();
        project.GlobalVariables.SetVariable(namespaceName, globVarName, syncobj);
    }
}
Теперь в месте, где код только читает и не вносит изменение в разделяемый ресурс используем следующие команды
Лок на чтение:
ReaderWriterLockSlim syncObject = (ReaderWriterLockSlim) project.GlobalVariables["test_namespace", "my_sync_object2"].Value;

for (int i = 0; i <= 50; i++)
{
    project.SendInfoToLog("1 - Пауза, перед локом", true);
    Thread.Sleep(300);
    syncObject.EnterReadLock();
    project.SendInfoToLog("1 - Заход в лок", true);
    Thread.Sleep(300);
    project.SendInfoToLog("1 - Выход из лока", true);
    syncObject.ExitReadLock();
}
в результате у нас строки 8,9,10 окажутся в локе Чтения. Все потоки с этим локом будут выполняться одновременно до тех пор пока у какого ни будь потока не сработает следующий код
Лок на запись:
ReaderWriterLockSlim syncObject = (ReaderWriterLockSlim) project.GlobalVariables["test_namespace", "my_sync_object2"].Value;

for (int i = 0; i <= 50; i++)
{
    project.SendInfoToLog("2 - Пауза, перед локом", true);
    Thread.Sleep(5000);
    syncObject.EnterWriteLock();
    project.SendInfoToLog("2 - Заход в лок", true);
    Thread.Sleep(5000);
    project.SendInfoToLog("2 - Выход из лока", true);
    syncObject.ExitWriteLock();

}
Как только выполнение кода войдет в лок Записи, то он получает исключительные права на однопоточное выполнение и все локеры чтения будут заблокированы на вход и будет инициализирована пауза до момента выхода всех потоков из лока Чтения.
Как только лок Записи выполнится, то сразу же все локеры Чтения получат право зайти в этот лок.

По апгреду лока, смотрите пример по ссылке, там просто. используются EnterUpgradeableReadLock и ExitUpgradeableReadLock
Еще хочу сделать замечание, что это я привел простенький пример, он немного не подходит для копипаста в рабочие проекты, так как тут есть опасность невыполнения команды выхода из лока, особенно чревато при работе с файлами и их непредвиденными ошибками. Поэтому надо обязательно применять конструкцию try{}cath{}finale{} и выход из лока помещать в finale
примерно вот так.
Безопасная конструкция:
ReaderWriterLockSlim syncObject = (ReaderWriterLockSlim) project.GlobalVariables["test_namespace", "my_sync_object2"].Value;

try
{
    syncObject.EnterWriteLock();
    // тута индуский код
}
catch
{
    // тута алярмы после индуского кода
}
finally
{
    syncObject.ExitWriteLock();
}
Пару тестовых шаблонов приложил, так сказать для наглядного закрепления материала :cd:
 

Вложения

Для запуска проектов требуется программа ZennoPoster.
Это основное приложение, предназначенное для выполнения автоматизированных шаблонов действий (ботов).
Подробнее...

Для того чтобы запустить шаблон, откройте программу ZennoPoster. Нажмите кнопку «Добавить», и выберите файл проекта, который хотите запустить.
Подробнее о том, где и как выполняется проект.

Кто просматривает тему: (Всего: 1, Пользователи: 0, Гости: 1)