Многопоточность в Delphi

Когда вам нужно выполнить фоновые операции или любую обработку, не связанную строго с пользовательским интерфейсом, вы можете следовать технически наиболее правильному подходу — создать отдельный поток выполнения в процессе.

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

Библиотека RTLDelphi предоставляет класс TThread, который позволит вам создавать потоки и управлять ими. Вы никогда не будете использовать класс TThread напрямую, потому что он абстрактный — класс с виртуальным абстрактным методом. Чтобы использовать потоки, вы всегда применяете подкласс TThread и его функции.

Класс TThread имеет конструктор с одним параметром (CreateSuspended), который позволяет вам выбрать, следует ли немедленно запустить поток или приостановить его до более позднего времени. Если объект потока запускается автоматически или при его возобновлении, он будет запускать свой метод Execute до тех пор, пока не будет завершен. Класс предоставляет защищенный интерфейс, который включает в себя два ключевых метода для подклассов потоков:

выполнение процедуры; виртуальная; абстрактная;

синхронизация процедур(Метод: TThreadMethod);

Метод Execute, объявленный как виртуальная абстрактная процедура, должен быть переопределен каждым классом потока. Он содержит основной код, который вы обычно помещаете в функцию потока при использовании системных функций.

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

Единственным параметром Synchronize является метод, который не принимает никаких параметров, как правило, метод того же класса потока. Поскольку вы не можете передать параметры этому методу, обычно некоторые значения сохраняются в данных объекта потока в методе Execute и используются в синхронизированных методах.

Delphi 7 включает в себя две новые версии Synchronize, которые позволяют синхронизировать метод с основным потоком, не вызывая его из объекта потока. Как новая перегруженная синхронизация, так и StaticSynchronize являются методами класса TThread и требуют потока в качестве параметра.

Еще один способ избежать конфликтов-использовать методы синхронизации, предлагаемые операционной системой. Блок SyncObjs определяет несколько классов VCL для некоторых из этих объектов синхронизации низкого уровня, таких как события (с классом TEvent и классом TSingleEvent) и критические разделы (с классом TCriticalSection). (События синхронизации не следует путать с событиями Delphi, так как эти два понятия не связаны.)

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

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

Весь код, приведенный в этой статье, описан с помощью DELPHI, среда отладки-Windowsme, Delphi 6. Функции API Windows, которые могут быть подробно описаны в документации MSDN .

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

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

Ниже приведен код:

 unit.pasfile for this example
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

//Определение класса окна
type
TForm1 = class (TForm)
ListBox1: TListBox;
ListBox2: TListBox;
Button1: TButton;
procedure Button1Click (Sender: TObject );
private
{Private declarations}
public
{Public declarations}
end;

//Определение класса потока
type
TListThread = class (TThread)
private
Str: String;
protected
procedureAddToList;//Add Str to the ListBox component
Procedure Execute; override;
public
LBox: TListBox;
end;
//Определение переменных
var
Form1: TForm1;
Letters: String = ‘AAAAAAAAAAAAAAAAAAAA’;//Global variable

implementation

{$ R * .dfm}

//Часть реализации класса потока
procedureTListThread.Execute;
var
I, J, K: Integer;
begin
for i: = 0 to 50 do
begin
for J: = 1 to 20 do
for K: = 1 to 1000 do//Cycle 1000 times to increase the probability of conflict
if letters [j] <‘Z ‘the then
Letters [J]: = succ (Letters [J])
the else
Letters [J]: =’ A ‘;
STR: = Letters;
synchronize (AddToList);//synchronous access VCL visual component
end;
end;

procedureTListThread.AddToList;
begin
LBox.Items.Add (str);//Add str to the list box
end;

//Window class implementation part
procedure TForm1.Button1Click (Sender: TObject);
var
th1, th2: TListThread ;
begin
Listbox1.Clear;
Listbox2.Clear;
th1: = tlistThread.Create (true);//Создать поток 1
th2: = tlistThread.Create (true);//Создать поток 2
th1.LBox: = listBox1;
th2.LBox : = listBox2;
th1.Resume;//Start execution of
th2.Resume;
end;

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

Добавьте блок SyncObjs в раздел uses кода unit1.pas в приведенном выше примере добавьте глобальную переменную критического раздела (TRTLCriticalSection) Critical1, добавьте код InitializeCriticalSection (Critical1) в событие FormCreate и добавьте код DeleteCriticalSection (Critical1) в событие FormDestroy, а затем измените TListThread.Выполните функцию, измененный код выглядит следующим образом :

procedureTListThread.Execute;
var
I, J, K: Integer;
begin
for i: = 0 to 50 do
begin
? EnterCriticalSection (Critical1);//Введите критический раздел
for J: = 1 to 20 do
for K: = 1 to 3000 do
if letters [j] <‘Z’ then
letters [j]: = succ (Letters [j])
else
letters [j]: = ‘A’;
str: = letters;
? LeaveCriticalSection (Critical1);//Выход из критического раздела
synchronize (addtolist);
end;
end;

Конечно, мы также можем использовать другие технологии синхронизации, такие как Мьютекс (mutexe), Семафор (semaphore) и т. Д. Эти технологии напрямую предоставляются нам Windows через API.

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

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

    //  InitializeCriticalSection (Critical1) in the creation of  the form//  DeleteCriticalSection (Critical1)  in the destruction of the form //  EnterCriticalSection (Critical1) in the thread   …… protected code  LeaveCriticalSection (Critical1)

  2. Mutex (взаимоисключающий объект) — это глобальный объект, используемый для последовательного доступа к ресурсам. Сначала мы настраиваем мьютекс, затем получаем доступ к ресурсам и, наконец, освобождаем мьютекс. При установке мьютекса, если другой поток (или процесс) попытается установить тот же мьютекс, поток остановится до тех пор, пока предыдущий поток (или процесс) не освободит мьютекс. Обратите внимание, что это может быть другой общий доступ к прикладным программам. Способ применения заключается в следующем:

    //intheformcreation  hMutex: = CreateMutex (nil, false, nil)  //in the form destruction  CloseHandle (hMutex)  //in the thread  WaitForSingleObject (hMutex, INFINITE)   …… protected code  ReleaseMutex (hMutex)

  3. Семафор (semaphore), который похож на мьютекс, но он может считать. Например, можно разрешить доступ к данному ресурсу одновременно трем потокам. На самом деле, Мьютекс-это семафор с максимальным количеством единиц. Способ применения заключается в следующем:

    //Intheformcreation  hSemaphore: = CreateSemaphore (nil, lInitialCount, lMaximumCount, lpName)  //In the form destruction  CloseHandle (hSemaphore)  //  WaitForSingleObject (hSemaphore, INFINITE)   … protected code  ReleaseSemaphore (hSemaphore, lReleaseCount, lpPreviousCount)

  4. Вы также можете использовать объект VCLTCriticalSection в Delphi, который определен в Syncobjs.pas.

При разработке многопоточных приложений и одновременном доступе нескольких потоков к общему ресурсу или данным необходимо рассмотреть проблему синхронизации потоков.

Образовательный портал 3TY.RU
Adblock
detector