Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.236.86.184] |
|
Сообщ.
#1
,
|
|
|
Предисловие:
Начиная с 2009 версии в Delphi была включена одна из реализаций Монитора. Это "объект" (если быть точнее то это структура) синхронизации потоков. Ембаркадеро, в документации, рекомендует использовать этот "объект" для совместного использования VCL компонентов из разных потоков. Реализация монитора находится в модуле System, из за чего не нужно ничего дополнительного объявлять что бы им воспользоваться. Внедрен в язык глубоко, в сам rtti. это можно увидеть по функции получения адреса монитора: 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 или любой его наследник). Начнем с самого простого... "Критические секции" TMonitor.Enter({Объект}); Техническая сторона При первом вызове создается монитор и связывается с объектом, для которого и был создан. У каждого объекта отдельный монитор. Размер выделяемой памяти выравнивается под кэш-линию. На моем процессоре (Intel Core 2 Quad) кэш-линии имеет размер 64 байта. Реально же для монитора требуется только 28 байт. После создания монитора объект занимается текущим потоком. При втором вызове функции проверяется является ли текущий поток владельцем этого объекта. Если это так-то увеличивается счетчик блокировки. Если же поток не является владельцем этого объекта, то сначала он пытается N раз в цикле занять объект (количество попыток указывается свойством SpinCount), а если это ему не удалось, он создает событие и засыпает. TMonitor.Enter({Объект},{Тайм аут}); По большому счету предыдущая процедура вызывает текущую функцию с параметром INFINITE. TMonitor.Exit({Объект}); Первое что делается в этой функции это проверка на владение этого объекта. Если текущий поток не является владельцем, то происходит ошибка. Поэтому нельзя занять объект в одном потоке, а освободить в другом (и это правильно). После проверки происходит освобождения объекта и посылается событие (пульс) что объект свободе. В документации ембаркадеро гарантирует, что очередь использования объекта работает по принципу FIFO. (что в принципе можно и увидеть в коде) Также есть еще одна функция TMonitor.TryEnter({Объект}); Вызов Enter c тайм аутом 0 и вызов TryEnter не является одним и тем же. Во второй функции она лишь смотрит, не занят ли объект (и занимает, если он им является). Также количество вызовов Enter должно соответствовать количеству вызовов Exit. пример: var Obj: TObject; //предположим, что объект уже создан I: Integer = 0; while True do begin TMonitor.Enter(Obj); try I := I + 1; finally TMonitor.Exit(Obj); end; end; TMonitor.SetSpinCount({Объект}, {Значение}); Немного посложнее... "Ожидание" TMonitor.Wait({Объект}, {Тайм аут}); Первое что происходит в функции это проверка владельца объекта. Поэтому нельзя ожидать события объекта не заняв его. Также при вызове функции сбрасываются все счетчики и объект делается свободным. По выходу из ожидания все значения счетчиков восстанавливаются. пример: 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; TMonitor.Wait({Объект события}, {Блокируемый объект}, {Тайм аут}); Функция похожа на предыдущую. Но первый обет является объектом события (от этого объекта функция ждет события для того что бы выйти из ожидания), а второй объект передается для освобождения. Только у второго объекта будет сброшен счетчик. Пример: //Предположим, что какие-то данные приходят из одного канала, но для разных потоков (клиентов). 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; TMonitor.Pulse({Объект}); TMonitor.PulseAll({Объект}); |