КАТЕГОРИИ: Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748) |
Средства синхронизации потоков
Классы и процедуры реализации многопоточности в Delphi Среда программирования Delphi предоставляет программисту доступ к возможностям создания многопоточных приложений с помощью специального класса TThread. С точки зрения операционной системы поток - это ее объект. При создании он получает дескриптор и отслеживается ОС. Объект класса TThread - это конструкция Delphi, соответствующая потоку. Этот объект создается до реального возникновения потока в системе и уничтожается после его исчезновения. Другая отличительная черта класса TThread - это совместимость с библиотекой визуальных компонентов VCL. Класс TThread включает в себя следующие элементы: 1. Constructor Create (Create Suspended: Boolean). В качестве аргумента он получает параметр CreateSuspended. Если его значение равно True, вновь созданный поток не начинает выполняться до тех пор, пока не будет сделан вызов метода Resume. В случае, если CreateSuspended имеет значение False, поток начинает исполнение и конструктор завершается; 2. DestructorDestroy; override. Деструктор Destroy вызывается, когда необходимость в созданном потоке отпадает. Деструктор завершает его и высвобождает все ресурсы, связанные с объектом TThread; 3. ProcedureResume. Метод Resume класса TThread вызывается, когда поток возобновляется после остановки, или если он был создан с параметром createSuspended, равным True; 4. ProcedureSuspend. Вызов метода Suspend приостанавливает поток с возможностью повторного запуска впоследствии. Метод Suspend приостанавливает поток вне зависимости от кода, исполняемого потоком в данный момент; выполнение продолжается с точки останова; 5. PropertySuspended: Boolean. Свойство Suspended позволяет программисту определить, не приостановлен ли поток. С помощью этого свойства можно также запускать и останавливать поток. Установив suspended в True, будет получен тот же результат, что и при вызове метода Suspend -- приостановку. Наоборот, установка Suspended в False возобновляет выполнение потока, как и вызов метода Resume; 6. FunctionWaitFor: Integer. Метод WaitFor предназначен для синхронизации потоков и позволяет одному потоку дождаться момента, когда завершится другой поток. Если внутри потока FirstThread имеется код: Code:= SecondThread.WaitFor; то это означает, что поток FirstThread останавливается до момента завершения потока SecondThread. Метод WaitFor возвращает код завершения ожидаемого потока; 7. PropertyPriority: TThreadPriority. Свойство Priority позволяет запросить и установить приоритет потоков. Приоритет определяет, насколько часто поток получает время процессора. Допустимыми значениями приоритета являются tpIdie, tpLowest, tpLower, tpNormal, tpHigher, tpHighest и tpTimeCritical; delphiмногопоточность программирование класс 8. ProcedureSynchronize (Method:TThreadMethod). Этот метод относится к секции protected, то есть может быть вызван только из потомков TThread. Delphi предоставляет программисту метод synchronize для безопасного вызова методов VCL внутри потоков. Метод synchronize дает гарантию, что к каждому объекту VCL одновременно имеет доступ только один поток. Аргумент, передаваемый в метод synchronize, - это имя метода, который производит обращение к VCL; 9. ProcedureExecute; virtual; abstract. Эта процедура является главным методом объекта TThread. В теле метода должен содержаться код, который представляет собой программу потока. Метод Execute класса TThread объявлен как абстрактный; 10. Property ReturnValue: Integer. Свойство ReturnValue позволяет узнать и установить значение, возвращаемое потоком по его завершении. Эта величина полностью определяется пользователем. По умолчанию поток возвращает ноль, но если программист захочет вернуть другую величину, то простое изменение свойства ReturnValue внутри потока позволит получить эту информацию другим потокам. Это, например, может пригодиться, если внутри потока в ходе его выполнения возникла какая-либо сложная ситуация.
§2. Средства Delphi для работы с потоками. Интерфейс Win32 API позволяет программисту управлять распределением времени между потоками; это распространяется и на приложения, написанные на Delphi. Операционная система планирует время процессора в соответствии с приоритетами потоков. Приоритет потока - величина, состоящая из двух составных частей: приоритета породившего поток процесса и собственно приоритета потока. Когда поток создается, ему назначается приоритет, соответствующий приоритета его процесса, породивший. В свою очередь, процессы могут иметь следующие классы приоритетов. · Real time; · Normal; · High; · Below normal; · Abovenormal; · Idle. Использование класса High ограничено процессами, которые должны завершаться за короткое время, чтобы не вызвать сбойной ситуации. Пример - процесс, который посылает сигналы внешнему устройству; причем устройство отключается, если не получит своевременный сигнал. Если у пользователя возникли проблемы с производительностью его приложения, было бы неправильно решать их просто за счет повышения его приоритета в high - такой процесс также влияет на всю ОС. Возможно, в этом случае следует модернизировать компьютер. Большинство процессов запускается в рамках класса с нормальным приоритетом. Нормальный приоритет означает, что процесс не требует какой-либо специальной внимания со стороны операционной системы. И наконец, процессы с фоновым приоритетом запускаются только в том случае, если в очереди Диспетчера задач нет других процессов. Привычные виды приложений, используя такой приоритет, - это программы сохранения экрана и системные агенты (systemagents). Программисты могут использовать фоновые процессы для организации завершающих операций и реорганизации данных. Примерами могут служить сохранение документа или резервное копирование базы данных. Приоритеты имеют значения от 0 до 31. Процесс, породивший поток, может впоследствии изменить его приоритет; в этой ситуации программист имеет возможность управлять скоростью отклика каждого потока. Базовый приоритет нити состоит из двух составляющих, однако это не означает, что он просто равную сумме. Нормальная практика для асимметричных потоков - это назначение потока, обрабатывающем введения, высшего приоритета, а всем остальным - низшего или даже приоритета idle, если этот поток должен выполняться только во время простоя системы. Класс TThread Delphi представляет программисту полный доступ к возможностям программирования интерфейса Win32. Для чего же тогда фирма Borland представила специальный класс для организации потоков? Программист не обязан разбираться во всех тонкостях механизмов, предлагаемых операционной системой. Класс должен инкапсулировать и упрощать программный интерфейс; класс TThread - прекрасный пример предоставления разработчику простого доступа к программированию потоков. Сам API поток не очень сложный, но предоставленные классом TThread возможности вообще замечательно просты. В двух словах, все, что необходимо сделать, - это перекрыть виртуальный метод Execute. Другая отличительная черта класса TThread - это гарантия безопасной работы с библиотекой визуальных компонентов VCL. Без использования класса TThread во время вызовов VCL могут возникнуть ситуации, требующие специальной синхронизации (см. Разд. «Проблемы при синхронизации потоков" далее в этом разделе). При создании он получает дескриптор и отслеживается ОС. Объект класса TThread - это конструкция Delphi, соответствующая потоку ОС. Этот объект VCL создается до реального возникновения потока в системе и уничтожается после его исчезновения. Изучение класса TThread начнем с метода Execute: procedureExecute; virtual; abstract; Это и есть код, исполняемый в создаваемом вами потоке TThread. Формальное описание Execute - метод abstract, но мастер создания нового объекта TThread создает пустой шаблон этого метода. С методом Execute можно тем самым закладывать в новый потоковый класс, что будет выполняться при его запуске.Если поток был создан с аргументом CreateSuspended, равным False, то метод Execute выполняется немедленно, иначе Execute выполняется после вызова метода Resume (см. Описание конструктора ниже). Если поток рассчитан на однократное выполнение каких-либо действий, то никакого специального кода завершения внутри Execute писать не надо. Если же в потоке будет выполняться какой цикл, и поток должен завершиться вместе с приложением, то условия окончания цикла должны быть примерно такими: procedureTMyThread. Execute; begin repeat DoSomething; UntilCancelConditionorTerminated; end; Здесь CancelCondition - личное условие завершения потока (исчерпание данных, окончание вычислений, поступление на вход того или иного символа и т.п.), а свойство Terminated сообщает о завершении потока (это свойство может быть установлено как изнутри потока, так и извне; скорее всего, завершается его процесс, породивший). Конструктор объекта: constructor Create (CreateSuspended: Boolean); получает параметр CreateSuspended. Если его значение равно True, вновь созданный поток не начинает выполняться до тех пор, пока не будет сделан вызов метода Resume. В случае, если параметр CreateSuspended имеет значение False, конструктор завершается и только тогда поток начинает выполнение. destructorDestroy; override; Деструкция Destroy вызывается, когда необходимость в созданном потоке отпадает. Деструкция завершает его и высвобождает все ресурсы, связанные с объектом TThread. functionTerminate: Integer; Для окончательного завершения потока (без последующего запуска) существует метод Terminate. Все, что происходит, - это установка свойства propertyTerminated: Boolean; в значение True. Таким образом, Terminate - это указание потоку завершиться, выраженное «в мягкой форме», с возможностью корректно освободить ресурсы. Если нужно немедленно завершить поток, следует использовать функцию Windows API TerminateThread. Метод WaitFor предназначен для синхронизации и позволяет одному потоку дождаться момента, когда завершится другой поток. Если пользователь внутри потока FirstThread, он пишет код Code = SecondThread. WaitFor; то это означает, что поток FirstThread приостанавливается до момента завершения потока SecondThread. Метод WaitFor возвращает код завершения ожидаемого потока (см. Свойство Returnvalue). property Handle: THandle read FHandle; propertyThreadID: THandle read FThreadID; Свойства Handle и ThreadID дают программисту непосредственный доступ к потоку средствами API Win32. Если разработчик хочет обратиться к потоку и управлять им, минуя возможности класса TThread, значения Handle и ThreadID могут быть использованы в качестве аргументов функций Win32 API. Например, если программист хочет перед продолжением выполнения приложения дождаться завершения сразу нескольких потоков, он должен вызвать функцию API waitForMuitipieObjects; для ее вызова нужен массив дескрипторов потоков. propertyPriority: TThreadPriority; Свойство Priority позволяет запросить и установить приоритет потоков. Приоритеты потоков в деталях описаны выше. Допустимыми значениями приоритета объектов TThread является tpidle, tpLowest, tpLower, tpNormai, tpHigher, tpHighest и tpTimeCritical. procedureSynchronize (Method:TThreadMethod); Этот метод относится к секции protected, то есть может быть вызван только из потомков TThread. Delphi предоставляет программисту метод Synchronize для безопасного вызова методов VCL внутри потоков. Во избежание конфликтных ситуаций, метод synchronize дает гарантию, что к каждому объекту VCL одновременно имеет доступ только один поток. Аргумент, передаваемый в метод Synchronize, - это имя метода, который производит обращение к VCL; вызов Synchronize с этим параметром - это то же, что и вызов самого метода. Такой метод (класса TThreadMethod) не должен иметь никаких параметров и не должен возвращать никаких значений. Например, в основной форме приложения нужно предусмотреть функцию procedureTMainForm. SyncShowMessage; begin ShowMessagedntToStr (ThreadListl. Count)); // Другиеобращенияк VCL end; а в потоке для показа сообщения писать не ShowMessage (IntToStr (ThreadListl. Count)); и даже не MainForm. SyncShowMessage; атолькотак: Synchronize (MainForm. SyncShowMessage); Производя любое обращение к объекту VCL из потока, стоит убедиться, что при этом используется метод Synchronize; иначе результаты могут оказаться непредсказуемыми. Это верно даже в том случае, если программист использует средства синхронизации, описанные ниже. procedureResume; Метод Resume класса TThread вызывается, когда поток возобновляет выполнение после остановки, или для явного запуска потока, созданного с параметром CreateSuspended, равным True. procedureSuspend; Вызов метода Suspend приостанавливает поток с возможностью повторного запуска впоследствии. Метод suspend приостанавливает поток независимо от кода, исполняемого потоком в данный момент; выполнения продолжается с точки останова. propertySuspended: Boolean; Свойство suspended позволяет программисту определить, не приостановлен ли поток. С помощью этого свойства можно запускать и останавливать поток. Установив свойство suspended в значение True, можно получить тот же результат, что и при вызове метода Suspend - прекращение. Наоборот, установка свойства Suspended в значение False возобновляет выполнение потока, как и вызов метода Resume. propertyReturnValue: Integer; Свойство ReturnValue позволяет узнать и установить значение, возвращается потоком после его завершения. Эта величина полностью определяется пользователем. По умолчанию поток возвращает ноль, но если программист захочет вернуть другую величину, то простая переустановка свойства ReturnValue внутри потока позволит получить эту информацию другим потокам. Это, например, может пригодиться, если внутри потока возникли проблемы, или с помощью свойства ReturnValue нужно вернуть число не прошедших орфографическую проверку слов. На этом завершается подробный обзор класса TThread. Для более близкого знакомства с потоками и классом DelphiTThread создается многопоточное приложение. Для этого нужно написать всего несколько строк кода и несколько раз щелкнуть мышью. Проще всего говорить о синхронизации, если создаваемый поток не взаимодействует с ресурсами других потоков и не обращается к VCL. MyCompThread = TComputationThread. Create (False); // Здесь можно что-либо делать, пока второй поток производит вычисления DoSomeWork; // Теперь ждем его завершения MyCompThread. WaitFor; Приведенная схема совершенно недопустима, если во время своей работы поток MyCompThread обращается к VCL с помощью метода synchronize. В этом случае поток ждет главного потока для обращения к VCL, а тот, в свою очередь, его - классическая тупик. За «спасением» следует обратиться к программному интерфейсу Win32. Он предоставляет богатый набор инструментов, которые могут понадобиться для организации совместной работы потоков. Главные понятия для понимания механизмов синхронизации - функции ожидания и объекты синхронизации. В Windows API предусмотрен ряд функций, позволяющих прекратить выполнение потока, вызвавшего эту функцию, вплоть до того момента, как будет измененное состояние какого объекта, называемого объектом синхронизации (под этим термином здесь понимается не объект Delphi, а объект операционной системы). Простейшая из этих функций - waitForSingieCbject - предназначена для ожидания одного объекта. К возможным вариантам относятся четыре объекта, которые разработаны специально для синхронизации: событие (event), взаимное исключение (mutex), семафор (semaphore) и таймер (timer). Но кроме специальных объектов можно организовать ожидание и других объектов, дескриптор которых используется в основном для других целей, но может применяться и для ожидания. К ним относятся: процесс (process), поток (thread), оповещения о смене в файловой системе (changenotification) и консольный ввод (consoleinput). Косвенно к этой группе может быть добавлена критическая секция (criticalsection).
Примечание Выше перечисленные средства синхронизации в основном инкапсулированы в состав классов Delphi. У программиста есть две альтернативы. С одной стороны, в состав библиотеки VCL включен модуль SYNCOBJS.PAS, содержащий классы для события (TEvent) и критической секции (TCriticalSection). С другой, с Delphi поставляется отличный пример IPCDEMOS, иллюстрирующий проблемы взаимодействия процессов и содержит модуль IPCTHRD.PAS с аналогичными классами - для того же события, взаимного исключения (TMutex), а также совместно используемой памяти (TSharedMem). Далее нужно перейти к подробному описанию объектов, используемых для синхронизации. Событие Объект типа событие (event) - простой выбор для задач синхронизации. Он подобен дверном звонке - звенит до тех пор, пока его кнопка находится в нажатом состоянии, извещая об этом факте окружающих. Аналогично, и объект может быть в двух состояниях, а «слышать» его могут многие потоков сразу. Класс TEvent (модуль SYNCOBJS.PAS) имеет два метода: setEvent и ResetEvent, которые переводят объект в активный и пассивный залог соответственно. Конструктор имеет следующий вид: constructor Create (EventAttributes: PSecurityAttributes; ManualReset, InitialState: Boolean; const Name: string); Здесь параметр initialstate - начальное состояние объекта, ManualReset - способ его сброса (перевода в пассивное состояние). Если этот параметр равен True, событие должно быть сброшено вручную. Иначе событие сбрасывается по мере того, как стартует хоть один поток, ждал данного объекта. На третьем методе: TWaitResult = (wrSignaled, wrTimeout, wrAbandoned, wrError); functionWaitFor (Timeout: DWORD): TWaitResult; Он дает возможность ожидать активизации события в течение Timeout миллисекунд. Типичных результатов на выходе waitFor два - wrsignaied, если произошла активизация события, и wrTimeout, если за время тайм-аута ничего не произошло. Если нужно (и допустимо!) Ждать бесконечно долго, следует установить параметр Timeout в значение INFINITE. Далее рассматривается маленький пример. В состав нового проекта включается объект типа TThread, наполнив его метод Execute следующим содержанием: Var res: TWaitResult; procedureTSimpleThread. Execute; begin e = TEvent. Create (nil, True, false, 'test'); repeat e. ReSetEvent; res = e. WaitFor (10000); Synchronize (Showlnfo); until Terminated; e. Free; end; procedureTSimpleThread. Showlnfo; begin ShowMessage (IntToStr (Integer (res))); end; На главной форме необходимо разместить две кнопки - нажатие одной из них запускает поток, нажатие второй активизирует событие: procedureTForml. ButtonlClick (Sender: TObject); begin TSimpleThread. Create (False); end; procedureTForml. Button2Click (Sender: TObject); begin e. SetEvent; end; Нажать первую кнопку. Результат (метод Showlnfo), тогда появился на экране, зависит от того, была ли нажата вторая кнопка или закончились отведенные 10 секунд. События используются не только для работы с потоками - некоторые процедуры операционной системы автоматически переключают их. К числу таким процедурам относятся отложено (overlapped) ввод / вывод и события, связанные с коммуникационными портами. Взаимные исключения Объект типа взаимное исключение (mutex) позволяет только одному потоку сейчас владеть им. Если продолжать аналогии, то этот объект можно сравнить с эстафетной палочкой. Класс, инкапсулирует взаимное исключение - TMutex - находится в модуле IPCTHRD.PAS (пример IPCDEMOS).Конструктор: constructor Create (const Name: string); задает имя создаваемого объекта. Сначала он не принадлежит никому. (Но функция API createMutex, вызываемое в нем, позволяет передать созданный объект тому потоку, в котором это произошло.) Далее метод function Get (TimeOut: Integer): Boolean; производит попытку течение Timeout миллисекунд завладеть объектом (в этом случае результат равен True). Если объект более не нужен, следует вызвать метод functionRelease: Boolean; Программист может использовать взаимное исключение, чтобы избежать считывания и записи общей памяти несколькими потоками одновременно. Семафор Семафор (semaphore) подобный взаимному исключению. Разница между ними в том, что семафор может управлять количеством потоков, которые имеют к нему доступ. Семафор устанавливается на предельное число потоков, которым доступ разрешен. Когда это число достигнуто, последующие потоки будут приостановлены, пока один или более потоков не отсоединятся от семафора и не освободят доступ. Критическая секция Работая в Delphi, программист может использовать объект типа критическая секция (criticalsection). Критические секции подобные взаимным исключением по сути, однако между ними существуют две главные отличия: · Взаимные исключения могут быть совместно использованы потоками в различных процессах, а критические секции - нет; · Если критическая секция принадлежит другому потоку, ожидающий поток блокируется вплоть до освобождения критической секции. В отличие от этого, взаимное исключение позволяет продолжение после окончания тайм-аута. Критические секции и взаимные исключения очень похожи. На первый взгляд, выигрыш от использования критической секции вместо взаимного исключения не очевиден. Критические секции, однако, более эффективны, чем взаимные исключения, так как используют меньше системных ресурсов. Взаимные исключения могут быть установлены на определенный интервал времени, по истечении которого выполнение продолжается; критическая секция всегда ждет столько, сколько нужно. Поток Поток может ожидать другой поток точно так же, как и другой процесс. Ожидания можно организовать с помощью функций API (как в только что рассмотренном примере), но удобнее это сделать с помощью метода TThread. WaitFor. Консольный ввод Консольный ввод (consoleinput) годится для потоков, которые должны ждать отклика на нажатие пользователем клавиши на клавиатуре. Этот тип ожидания может быть использован в программе дуплексной связи (chat). Один поток при этом ждать получения символов; второй - отслеживать ввод пользователя и затем посылать набранный текст ожидающему приложении. Извещение об изменении в файловой системе. Этот вид объекта ожидания очень интересный и незаслуженно мало известен. Были рассмотрены практически все варианты того, как один поток может подать сигнал другому.
Дата добавления: 2015-08-31; Просмотров: 654; Нарушение авторских прав?; Мы поможем в написании вашей работы! Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет |