Использование многопоточности в дополнениях Delphi




Лекция

Тема: Создание многотекущих дополнений у ОС Windows

План

Концепция потоков

Использование многопоточности в дополнениях Delphi

Запуск потока.

Синхронизации потоков

Критические секции

Концепция потоков

Потек (thread) - это объект операционной системы, которая являет собой отдельный путь выполнения программы внутри определенного процесса. Каждое дополнение Win32 имеет, по крайней мере, один поток, обычно называемый первичным, или главным, но программы имеют право создавать дополнительные потоки, предназначенные для выполнения других заданий.

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

Использование многопоточности в дополнениях Delphi

Следовательно, давайте определимся, что под словом " потек" мы имеем в виду именно Thread, который еще имеет название "нить". Нередко встречаются на форумах мысли, что потоки не нужны вообще, любую программу можно написать так, что она будет замечательно работать и без них. Конечно, если не делать ничего серьезнее "Hello World" это так и есть, но если постепенно набирать опыт, рано или поздно любой программист подойдет к возможности " плоского" кода, возникнет необходимость распараллеливать задание. А некоторые задачи вообще нельзя реализовать без использования потоков, например работа с сокетами, COM -портом, длительное ожидание каких-либо событий, и так далее

Всем известно, что Windows система многозадачна. Проще говоря, это значит, что несколько программ могут работать одновременно под управлением ОС. Все мы открывали диспетчер заданий и видели список процессов. Процесс - это экземпляр выполняемой программы. В действительности сам по себе он ничего не выполняет, он создается во время запуска программы, содержит в себе служебную информацию, через которую система с ним работает, так же ему выделяется необходимая память под код и данные. Для того, чтобы программа заработала, в нем создается поток. Любой процесс содержит в себе хотя бы один поток, и именно он отвечает за выполнение кода и получает на это процессорное время. Этим и достигается мнимая параллельность работы программ, или, как ее еще называют, псевдопараллельность. Почему мнимая? Но потому, что реально процессор в каждый момент времени может выполнять только один участок кода. Windows раздает процессорное время всех потоков в системе по очереди, тем же создается впечатление, что они работают одновременно. Реально работают параллельно потоки могут быть только на машинах с двумя и больше процессорами.

Для создания дополнительных потоков в Delphi существует базовый класс TThread, от него мы и будем наследоваться при реализации своих потоков. Для того, чтобы создать " скелет" нового класса, можно выбрать в меню File - New - Thread Object, Delphi создаст новый модуль с заготовкой этого класса. Для наглядности, опишем его в модуле формы. Как Вы видите, в этой заготівці прибавлено один метод - Execute. Именно его нам и надо переопределить, код внутри него и будет работать в отдельном потоке. И так, попробуем написать пример - запустим в потоке бесконечный цикл:

TNewThread = class (TThread)

private

(Private declarations)

protected

procedure Execute; override;

end;

 

var

Form1: TForm1;

 

implementation

($ R *. dfm)

(TNewThread)

 

procedure TNewThread.Execute;

begin

while true do (ничего не делаем);

end;

 

procedure TForm1.Button1Click (Sender: TObject);

var

NewThread: TNewThread;

begin

NewThread: = TNewThread.Create (true);

NewThread.FreeOnTerminate: = true;

NewThread.Priority: = tpLower;

NewThread.Resume;

end;

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

NewThread: = TNewThread.Create (true);

здесь мы создали экземпляр класса TNewThread. Конструктор Create имеет всего один параметр - CreateSuspended типа boolean, который указывает, запустить новый поток сразу после создания (если false), или дождаться команды (если true).

New.FreeOnTerminate: = true;

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

NewThread.Priority: = tpLower;

Свойство Priority, если вы еще не догадались из названия, устанавливает приоритет потока. Каждый поток в системе имеет свой приоритет. Если процессорного времени не хватает, система начинает распределять его в соответствии с приоритетами потоков. Свойство Priority может принимать следующие значения:

• tpTimeCritical - критический

• tpHighest - очень высокий

• tpHigher - высокий

• tpNormal - средний

• tpLower - низкий

• tpLowest - очень низкий

• tpIdle - поток работает во время простоя системы

Ставить высокие приоритеты потоков не стоит, если этого не требует задание, потому что это сильно нагружает систему.

NewThread.Resume;

 

Запуск потока.

Это был пример создания потоков. Но не все так просто. Казалось бы - пишем любой код внутри метода Execute и все, но, потоки имеют одно неприятное свойство - они ничего не знают друг о друге. Это значит, допустимо, Вы пытаетесь из другого потока изменить свойство какого-либо компонента в форме. Как известно, VCL однопоточный, весь код внутри программы выполняется последовательно. Допустимо, в процессе работы изменились какие-то данные внутри классов VCL, система отбирает время у основного потока, передает по кругу другим потокам и возвращает назад, при этом выполнение кода продолжается с того места, где прекратилось. Если мы из своего потока что-то изменяем, например, на форме, задействовано много механизмов внутри VCL (напомним, выполнение основного потока пока "прекращено"), соответственно за это время успеют измениться любые данные. И здесь вдруг время опять отдается основному потоку, он спокойно продолжает свое выполнение, но даны уже изменены! К чему это может привести - предусмотреть нельзя. Вы можете проверить это тысячу раз, и ничего не случится, а на тысячу первый программа свалится. И это относится не только к взаимодействию дополнительных потоков с главным, но и к взаимодействию потоков между собой. Писать такие ненадежные программы обычно нельзя.

 

Синхронизации потоков

Если вы создали шаблон класса автоматически, то, наверное, заметили комментарий, какой Delphi поместил в новый модуль. Он говорит: " Methods and properties of objects in visual components can only be used in a method called using Synchronize". Это значит, что обращение к визуальным компонентам возможно только путем вызова процедуры Synchronize. Давайте рассмотрим пример, но теперь наш поток не будет разогревать процессор зря, а будет делать что-либо полезное, например, прокручивать ProgressBar на форме. Как параметр в процедуру Synchronize передается метод нашего потока, но сам он передается без параметров. Параметры можно передать, прибавив поля нужного типа в описание нашего класса. У нас будет одно поле - тот же прогресс:

 

TNewThread = class (TThread)

private

Progress: integer;

procedure SetProgress;

protected

procedure Execute; override;

end;

...

 

procedure TNewThread.Execute;

var

i: integer;

begin

for i: = 0 to 100 do

begin

sleep (50);

Progress: = i;

Synchronize (SetProgress);

end;

end;

 

procedure TNewThread.SetProgress;

begin

Form1.ProgressBar1.Position: = Progress;

end;

Вот теперь ProgressBar двигается, и это полностью безопасно. А безопасно вот почему: процедура Synchronize на время приостанавливает выполнение нашего потока, и передает управление главного потока, то есть SetProgress выполняется в главном потоке. Это надо запомнить, потому что некоторые допускают ошибки, выполняя внутри Synchronize длительную работу, при этом, что очевидно, форма зависает на длительное время. Поэтому используйте Synchronize для выведения информации - тот же двигатель прогресса, обновления заглавий компонентов и тому подобное

Вы наверное заметили, что внутри цикла мы используем процедуру Sleep. В одинпотоковом дополнении Sleep используется редко, а вот в потоках его использовать очень удобно. Пример - бесконечный цикл, пока не выполнится какое-то условие. Если не вставить туда Sleep мы будем просто нагружать систему напрасной работой. Так работает Synchronize. Но есть еще один достаточно удобный способ передать информацию форме - посылка сообщения. Давайте рассмотрим и его. Для этого объявим константу:

 

const

PROGRESS_POS = WM_USER 1;

 

В объявление класса формы прибавим новый метод, а потом и его реализацию:

 

TForm1 = class (TForm)

Button1: TButton;

ProgressBar1: TProgressBar;

procedure Button1Click (Sender: TObject);

private

procedure SetProgressPos (var Msg: TMessage); message PROGRESS_POS;

public

(Public declarations)

end;

...

 

procedure TForm1.SetProgressPos (var Msg: TMessage);

begin

ProgressBar1.Position: = Msg.LParam;

end;

 

Теперь мы немного изменим, можно сказать даже упростим, реализацию метода Execute нашего потока:

procedure TNewThread.Execute;

var

i: integer;

begin

for i: = 0 to 100 do

begin

sleep (50);

SendMessage (Form1.Handle, PROGRESS_POS, 0, i);

end;

end;

 

Используя функцию SendMessage, мы посылаем окну программы сообщения, один из параметров которого содержит нужный нам прогресс. Сообщение становится в очередь, и в соответствии с этой очередью будет обработано главным потоком, где и выполнится метод SetProgressPos. Но здесь есть один нюанс: SendMessage, как и в случае с Synchronize, приостановит выполнение нашего потока, пока основной поток не обработает сообщения. Если использовать PostMessage этого не случится, наш поток отправит сообщение и продолжит свою работу, а уже когда оно там будет проработано - неважно. Какую из этих функций использовать - решать вам, все зависит от задания.

Вот, в принципе, мы и рассмотрели основные способы работы с компонентами VCL из потоков. А как быть, если в нашей программе не один новый поток, а несколько? И нужно организовать работу с одними и теми же данными? Здесь нам на помощь приходят другие способы синхронизации. Один из них мы и рассмотрим. Для его реализации нужно прибавить в проект модуль SyncObjs.

 

Критические секции

Работают они таким способом: внутри критической секции может работать только один поток, другие ожидают его завершения. Чтобы лучше понять, везде приводят сравнения с узкой трубой: представьте, с одной стороны "толпятся" потоки, но в трубу может "пролізти" только один, а когда он "пролізе" - начнет движение второй, и так по порядку. Еще проще понять это на примере и тем же ProgressBar 'ом. Следовательно, запустите один из примеров, приведенных раньше. Нажмите на кнопку, подождите несколько секунд, а потом нажмите еще раз. Что происходит? ProgressBar начал прыгать. Прыгает потому, что у нас работает не один поток, а два, и каждый из них передает разные значения прогрессу. Теперь немного переделаем код, в событии onCreate формы создадим критическую секцию:

var

Form1: TForm1;

CriticalSection: TCriticalSection;

...

procedure TForm1.FormCreate (Sender: TObject);

begin

CriticalSection: = TCriticalSection.Create;

end;

 

У TCriticalSection есть две нужны нам методу, Enter и Leave, соответственно вход и выход из нее. Поместим наш код в критическую секцию:

 

procedure TNewThread.Execute;

var

i: integer;

begin

CriticalSection.Enter;

for i: = 0 to 100 do

begin

sleep (50);

SendMessage (Form1.Handle, PROGRESS_POS, 0, i);

end;

CriticalSection.Leave;

end;

Попробуйте запустить программу и нажать несколько раз на кнопку, а потом посчитайте, сколько раз пройдет прогресс. Понятно, в чем суть? Первый раз, нажимая на кнопку, мы создаем поток, он занимает критическую секцию и начинает работу. Нажимаем второй - создается второй поток, но критическая секция занята, и он ожидает, пока ее не освободит первый. Третий, четвертый - все пройдут только по-черзі.

Критические секции удобно использовать при обработке одних и тех же данных (списков, массивов) разными потоками.

 

Контрольные вопросы

1. Что такое потек?

2. Какие Вы знаете типы многозадачности у ОС Windows?

3. Что такое процесс?

4. В чем отличие потока от процесса?

5. Для чего необходимая процедура Synchronize?

6. Для чего необходимы критические секции?

7. Какие Вы знаете приоритеты потоков?

8. Для чего необходимый метод Execute?

9. Как можно определить количество потоков в запущенном процессе?

 

Литература

1.Матчо Дж. Delphi 2: Руководство для профессионалов: Пер. из англ.- Спб.: Вvh -сант-петербург, 1997.-784 с.

2.СкусновА.Л. Справочник по компонентам Delphi 3.-М.: Приор, 1998.-288 с.

3. Культін н.Б. Программирование в Турбо Pascal 7.0 и Delphi: (Учебник) -СПб.: Вvh -сант-петербург, 1999.-234 с.

4. Епанешников А. М., Епанешников в.А. Программирование в среде Delphi: Учебный Посібник.-м.: Диалог МИФЕ.,1997.- Ч4. Работа с базами данных. Организация справочной системи.-1998.-400 с.

5. Бобровський с.Н. Delphi 5: Учебный курс.-СПб: ДЕСС: Інфорком-пресс, 2000.-638 с.

6.Архангельский а.Я. 100 компонентов общего назначения библиотеки Delphi 5.-М.: Бином, 1999.-266 с.

7.Епанешников А. М., Епанешников в.А. Delphi5. Базы Даних.-м: ДИАЛОГОВОМ МИФЕ, 2000.-416 с.

 



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-04-15 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: