Организация многопоточной работы в Delphi
Поняте многопоточного приложения. Создание потоков. Потоки в Delphi выполняют функцию имитации псевдопараллельной работы приложения. Приложение Delphi, умеющее создать несколько потоков, получит больше времени операционной системы, и соответственно сможет выполнить больший объём работы. Создать дополнительный поток в Delphi поможет объект TThread. Ввести объект TThread в программу можно двумя способами: 1. с помощью Мастера; 2. вручную. Мастер создания дополнительного потока в Delphi создаёт отдельный модуль, в рамках которого выполняется поток. File -> New -> Other... В появившейся табличке выбора найдём TThread Object. Появится окошко, в верхнюю строку которого (Class Name) введём имя нашего будущего потока: MyThread.
Создание потока с помощью мастера В результате будет создан модуль, содержащий заготовку кода, реализующего дополнительный поток Delphi. unit Unit2; // Имя модуля, содержащего поток. При сохранении его можно изменить. interface uses Classes; type MyThread = class(TThread) //MyThread - заданное нами имя потока. private { Private declarations } protected procedure Execute; override; end; implementation { Important: Methods and properties of objects in visual components can only be used in a method called using Synchronize} { MyThread } procedure MyThread.Execute; begin { Place thread code here } end; end.
Создание потока вручную В первом способе класс MyThread был создан мастером в дополнительном модуле. Второй способ состоит в том, что можно создать такой класс в рамках одного из уже существующих модулей программы, например, в модуле Unit1: unit Unit1; //Обычный модуль в котором описывается основная программа … type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); end; //Здесь необходимо описать класс TMyThread: TMyThread = class(TThread) private { Private declarations } protected procedure Execute; override; end;
Создание потока вручную var Form1: TForm1; //Нужно ввести переменную класса TMyThread MyThread: TMyThread; implementation {$R *.dfm} //Нужно создать процедуру Execute, уже описанную в классе TMyThread procedure TMyThread.Execute; begin //Здесь описывается код, который будет выполняться в потоке end; Если поток создаётся мастером, т.е. в другом модуле, то не забудьте в основном модуле описать переменную - экземпляр потока. Также, поскольку класс потока описан в другом модуле, имя этого модуля необходимо добавить в секцию uses.uses
Создание потока вручную Теперь можно запускать поток, даже если в его процедуре Execute нет ни единого оператора. //Запускать поток будем нажатием на кнопку: procedure TForm1.Button1Click(Sender: TObject); begin //Вначале нужно создать экземпляр потока: MyThread:=TMyThread.Create(False); //Параметр False запускает поток сразу после создания, True - запуск впоследствии, методом Resume //Далее можно указать параметры потока, например приоритет: MyThread.Priority:=tpNormal; end; Если в основной программе попробовать выполнить такой цикл: while True do; то приложение зависнет. А если поместить его в процедуру Execute. При нажатии на кнопку наш бесконечный цикл будет непрерывно выполняться в потоке, однако и приложение как целое не зависнет.
Завершение работы потока Поток обладает возможностями, позволяющими из основной программы передать ему приказ прекратить работу. Метод потока Terminate устанавливает свойство Terminated потока в True. Анализируя это свойство, поток может понять, что он должен завершить работу. procedure TMyThread.Execute; begin while True do if MyThread.Teminated then break; end; Этот код выполняет бесконечный цикл. Однако, при выполнении в основной программе оператора: MyThread.Terminate; цикл завершается, и поток прекращает свою работу.
Приоритеты потоков При работе с потоками необходимо учитывать приоритет создаваемых потоков. Так, если в предыдущем примере запустить не один поток, а два или больше, нажав на кнопку несколько раз, то компьютер станет очень заметно "тормозить". Это происходит потому, что приоритет по умолчанию новых потоков - нормальный. Можно уменьшить его, задав MyThread.Priority:=tpLower ; Этого достаточно, чтобы компьютер чувствовал себя более свободно. ПриоритетОписание tpIdle Низший приоритет. Поток получает время только тогда, когда операционая система находится в состоянии простоя. tpLowestПриоритет на два пункта ниже нормального tpLowerПриоритет на один пункт ниже нормального tpNormalНормальный приоритет tpHigherПриоритет на один пункт выше нормального tpHighestПриоритет на два пункта выше нормального tpTimeCritical Максимальный приоритет. Приоритет на уровне функций ядра операционной системы.
Синхронизация потоков При использовании в приложении нескольких потоков необходимо гарантировать, что в данный момент только один из потоков может иметь доступ к свойствам и методам объекта VCL - визуального компонента Delphi. Для выполнения такой синхронизации в Delphi применяется специальный метод Synchronize, в рамках которого и нужно вызывать процедуры, модифицирующие свойства визуальных компонентов. Процедура Synchronize использует в качестве параметра те процедуры, в которых происходит модификация свойств визуальных компонентов, и блокирует одновременный доступ к компоненту нескольких потоков. {Важно: Методы и свойства объектов в визуальных компонентах могут вызываться только в методе Synchronize, например:} procedure MyThread.UpdateCaption; begin Form1.Caption := 'Updated in a thread'; end; procedure MyThread.Execute; begin Synchronize(UpdateCaption); end; В данном случае поток используется для изменения заголовка Формы. Изменение заголовка происходит в процедуре UpdateCaption.
Синхронизация потоков Метод Synchronize выполняется в главном потоке приложения. Поэтому, работая с несколькими потоками в приложении и применяя метод Synchronize, нужно учитывать, что: во-первых, частый вызов Synchronize тормозит выполнение приложения; во-вторых, если практически все процедуры выполняющегося потока выполняются с использованием метода Synchronize, то смысла в создании такого потока нет - всё равно его работа пройдёт в главном потоке. procedure TMyThread.UpdateCaption; begin Form1.Caption:=IntToStr(Cap); //Cap – глобальная переменная end; procedure TMyThread.Execute; begin while True do begin Cap:=Cap+1; Synchronize(UpdateCaption); sleep(100); end; end;
Многопоточные приложения Разработать приложение, которое генерирует 2 массива случайных чисел (по элементов). В отдельных потоков реализовать вычисление массива суммы и массива произведения из двух исходных. TMyThread = class(TThread) //новый класс потока private oper: char; //перемення для получения операции из внешней protecte // программы procedure ShowMas1; //процедура вывода элемнта массива mas1 procedure ShowMas2; //процедура вывода элемнта массива mas2 procedure Execute; override; end;
Многопоточные приложения //Процедура генерации массивов и вывод их на форму procedure TForm1.Button2Click(Sender: TObject); var i:integer; begin for i:=1 to do begin mas1[i]:=random(200); mas2[i]:=random(200); Memo1.Lines.Add(IntToStr(mas1[i])); Memo2.Lines.Add(IntToStr(mas2[i])); end; //глобальные переменные программы var Form1: TForm1; mas1,mas2,mas3,mas4:array[ ] of integer; MyThread1,MyThread2:TMyThread; //переменные потоков kol1:integer; //номер элемента массива mas1 kol2:integer; //номер элемента массива mas2
Многопоточные приложения //процедура создания mas3 (генерация первого потока) procedure TForm1.Button1Click(Sender: TObject); begin MyThread1:=TMyThread.Create(False); MyThread1.Priority:=tpLowest; //приоритет потока MyThread1.oper:='+'; //передача операции в поток end; //процедура создания mas4 (генерация второго потока) procedure TForm1.Button3Click(Sender: TObject); begin MyThread2:=TMyThread.Create(False); MyThread2.Priority:=tpLowest; //приоритет потока MyThread2.oper:='*'; //передача операции в поток end;
Многопоточные приложения //процедура потока procedure TMyThread.Execute; begin case oper of '+': begin //если в поток передали символ + for kol1:=1 to do begin mas3[kol1]:=mas1[kol1]+mas2[kol1]; Synchronize(ShowMas1); //вывод элемента массива //sleep(30); пауза в 30 мс end; '*': begin //если в поток передали символ * for kol2:=1 to do begin mas4[kol2]:=mas1[kol2]*mas2[kol2]; Synchronize(ShowMas2); //вывод элемента массива //sleep(30); пауза в 30 мс end;
Многопоточные приложения //Процедура для вывода элемента массива mas3 из потока procedure TMyThread.ShowMas1; begin Form1.Memo3.Lines.Add(IntToStr(mas3[kol1])); end; //Процедура для вывода элемента массива mas3 из потока procedure TMyThread.ShowMas2; begin Form1.Memo4.Lines.Add(IntToStr(mas4[kol2])); end;