На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: jack128, Rouse_, Krid
  
    > Монитор (синхронизация)
      Предисловие:
      Начиная с 2009 версии в Delphi была включена одна из реализаций Монитора. Это "объект" (если быть точнее то это структура) синхронизации потоков. Ембаркадеро, в документации, рекомендует использовать этот "объект" для совместного использования VCL компонентов из разных потоков.
      Реализация монитора находится в модуле System, из за чего не нужно ничего дополнительного объявлять что бы им воспользоваться. Внедрен в язык глубоко, в сам rtti. это можно увидеть по функции получения адреса монитора:
      ExpandedWrap disabled
        class function TMonitor.GetFieldAddress(AObject: TObject): PPMonitor;
        begin
          Result := PPMonitor(PByte(AObject) + AObject.InstanceSize - hfFieldSize + hfMonitorOffset);
        end;

      Подробнее о том что это и с чем его едят можно почитать на WikiPedia
      Если вкратце, то это помесь критической секции с событиями.

      У TMonitor есть ряд статических методов: Enter, TryEnter, Exit, Wait, Pulse, PulseAll. Также есть функции: MonitorEnter, MonitorTryEnter, MonitorExit, MonitorWait,MonitorWait, MonitorPulse, MonitorPulseAll. Которые вызывают аналогичные, из структуры TMonitor и имеют свойство inline. Так сказать, кому более что нравится (лично мне более по душе первый вариант).
      В качестве параметра передается "живой" объект (TObject или любой его наследник).

      Начнем с самого простого...
      "Критические секции"
      ExpandedWrap disabled
        TMonitor.Enter({Объект});
      Процедура занимает объект. Если же объект занят в это время то поток "засыпает" до того момента пока объект не освободится. Ничего не возвращает. Из объекта владельца можно вызывать несколько раз.
      Техническая сторона
      При первом вызове создается монитор и связывается с объектом, для которого и был создан. У каждого объекта отдельный монитор. Размер выделяемой памяти выравнивается под кэш-линию. На моем процессоре (Intel Core 2 Quad) кэш-линии имеет размер 64 байта. Реально же для монитора требуется только 28 байт. После создания монитора объект занимается текущим потоком.
      При втором вызове функции проверяется является ли текущий поток владельцем этого объекта. Если это так-то увеличивается счетчик блокировки.
      Если же поток не является владельцем этого объекта, то сначала он пытается N раз в цикле занять объект (количество попыток указывается свойством SpinCount), а если это ему не удалось, он создает событие и засыпает.

      ExpandedWrap disabled
        TMonitor.Enter({Объект},{Тайм аут});
      Делает все тоже самое что и предыдущая процедура. Отличается только тем, что имеет второй параметр времени ожидания и возвращает результат ожидания.
      По большому счету предыдущая процедура вызывает текущую функцию с параметром INFINITE.
      ExpandedWrap disabled
        TMonitor.Exit({Объект});

      Первое что делается в этой функции это проверка на владение этого объекта. Если текущий поток не является владельцем, то происходит ошибка. Поэтому нельзя занять объект в одном потоке, а освободить в другом (и это правильно). После проверки происходит освобождения объекта и посылается событие (пульс) что объект свободе.
      В документации ембаркадеро гарантирует, что очередь использования объекта работает по принципу FIFO. (что в принципе можно и увидеть в коде)

      Также есть еще одна функция
      ExpandedWrap disabled
        TMonitor.TryEnter({Объект});
      Функция пробует занять объект. Результат ее попытке она вернет в виде булевого значения.
      Вызов Enter c тайм аутом 0 и вызов TryEnter не является одним и тем же. Во второй функции она лишь смотрит, не занят ли объект (и занимает, если он им является).

      Также количество вызовов Enter должно соответствовать количеству вызовов Exit.

      пример:
      ExpandedWrap disabled
        var
          Obj: TObject; //предположим, что объект уже создан
          I: Integer = 0;
         
          while True do
          begin
            TMonitor.Enter(Obj);
            try
              I := I + 1;
            finally
              TMonitor.Exit(Obj);
            end;
          end;
      В этом примере гарантировано каждый поток по очереди будет прибавлять к переменной I единицу.
      ExpandedWrap disabled
        TMonitor.SetSpinCount({Объект}, {Значение});
      Задает значение количеству попыток занять поток, перед тем как "уснуть" на событии. По умолчанию является 0. Также нельзя присвоить ему другое значение, если в системе только одно ядро процессора.

      Немного посложнее...
      "Ожидание"
      ExpandedWrap disabled
        TMonitor.Wait({Объект}, {Тайм аут});
      Функция ожидания события на объект. В качестве параметров передается объект и время тайм аута. Результатом будет булево значение успешности ожидания.
      Первое что происходит в функции это проверка владельца объекта. Поэтому нельзя ожидать события объекта не заняв его. Также при вызове функции сбрасываются все счетчики и объект делается свободным. По выходу из ожидания все значения счетчиков восстанавливаются.

      пример:
      ExpandedWrap disabled
        var
          Obj: TObject;
         
        //Код первого потока
        TMonitor.Enter(Obj);
        try
          {некий код}
          TMonitor.Wait(Obj, INFINITE);
          {некий код}
        finally
          TMonitor.Exit(Obj);
        end;
         
        //Код второго потока
        TMonitor.Enter(Obj);
        try
          {некий код}
        finally
          TMonitor.Exit(Obj);
        end;
      Первый поток займет объект и начнет работать, второй же объект будет ожидать освобождения. После того как первый объект вызовет Wait объект станет свободным и второй поток начнет свою работу. Это нужно учитывать при проектировании синхронизации.

      ExpandedWrap disabled
        TMonitor.Wait({Объект события}, {Блокируемый объект}, {Тайм аут});

      Функция похожа на предыдущую. Но первый обет является объектом события (от этого объекта функция ждет события для того что бы выйти из ожидания), а второй объект передается для освобождения. Только у второго объекта будет сброшен счетчик.

      Пример:
      ExpandedWrap disabled
        //Предположим, что какие-то данные приходят из одного канала, но для разных потоков (клиентов).
        var
          //Глобальный объект для обмена данных
          GlobalRes: TObject;
          //Объект синхронизации первого клиента
          Client1: TObject;
          //Объект синхронизации первого клиента
          Client1: TObject;
          
        //Код первого потока-клиента
        TMonitor.Enter(GlobalRes);//Занимаем глобальный объект
        try
           //Ждем сигнала от Client1 и "освобождаем" GlobalRes
           TMonitor.Wait(Client1, GlobalRes, INFINITE);
           {Некие шаманства над глобальным объектом}
        finally
          TMonitor.Exit(GlobalRes);//отпускаем глобальный объект
        end;
         
        //Код второго потока-клиента
        TMonitor.Enter(GlobalRes);//Занимаем глобальный объект
        try
           //Ждем сигнала от Client2 и "освобождаем" GlobalRes
           TMonitor.Wait(Client1, GlobalRes, INFINITE);
           {Некие шаманства над глобальным объектом}
        finally
          TMonitor.Exit(GlobalRes);//отпускаем глобальный объект
        end;
         
        //Код третьего потока который помещает данные в глобальный объект
        TMonitor.Enter(GlobalRes);//Занимаем глобальный объект
        try
           {Что то делаем с GlobalRes}
           if {GlobalRes.данные_для_первого_клиента} then
             TMonitor.Pulse(Client1)
           else
             TMonitor.Pulse(Client2);
        finally
          TMonitor.Exit(GlobalRes);//отпускаем глобальный объект
        end;
      пс. по причине того что глобальный объект будет занят во время "пульса", то поток клиента начнет работать только после того как тот будет освобожден. Это дает шанс вызывать пульс в любом месте Enter/Exit.


      ExpandedWrap disabled
        TMonitor.Pulse({Объект});
      процедура посылает событие для потока который его ожидает (ожидать событие можно функцией Wait если кто забыл). Принадлежность к потоку не проверяется, поэтому можно вызвать из любого потока и любого места. Будет пробужден следующий поток в очереди (в очереди ожидания, если потоков больше одного). Порядок очереди FIFO.

      ExpandedWrap disabled
        TMonitor.PulseAll({Объект});
      Процедура похожа на предыдущую за исключением того что она посылает событие сразу всем потокам. Ее удобно использовать, если у вас 20 потоков ожидают событие, а вам нужно закрыть программу.
      0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
      0 пользователей:


      Рейтинг@Mail.ru
      [ Script execution time: 0,0527 ]   [ 16 queries used ]   [ Generated: 29.03.24, 01:49 GMT ]