A Delphi programozási nyelv

Párhuzamosság

Szintaxis

 Delphiben a párhuzamosságot legegyszerűbben a TThread osztály felhasználásával tudjuk megvalósítani. Ez egy absztrakt osztály, ezért közvetlenül nem használhatjuk, azonban a belőle származtatott alosztályok képesek lesznek a párhuzamosságra.

 Természetesen Delphiben is lehetőség van a Windowsban megszokott API hívásokon keresztül (pl.: CreateThread) megvalósítani a szálakat, azonban a TThread osztály használata sokkal kényelmesebb, és egy-két esettől eltekintve elegendő szolgáltatást nyújt párhuzamos programok írásához. Egyedül az osztott erőforrások használatánál felmerülő problémáknál fontos a Windows API hívások használata, ezek azonban (mint később látni fogjuk) jól kombinálhatók a TThread osztállyal.

A TThread osztály

 A TThread osztály legfontosabb (virtuális) absztrakt művelete az Execute() metódus, melyet minden leszármazottban felül kell definiálni. Ez tartalmazza a szál fő kódját. Szálat a Create konstruktor meghívásával hozhatunk létre, melynek egy Boolean paramétere van (CreateSuspended), mellyel azt adjuk meg, hogy a szál felfüggesztődjön (true), vagy a létrehozás után azonnal elinduljon (false).

Ezek alapján egy rövid példa:

type TMyThread = class(TThread) protected procedure Execute; override; end;

 Ezt a TMyThread osztályt példányosítva elindul annyi szál, amennyit csak példányosítunk belőle. Egy ajánlás, hogy egyprocesszoros gépen nem használjunk 16 szálnál többet, mert nagyon rámehet a sebesség rovására.

 Az egyes szálaknak külön-külön prioritás adható, és ilyen mértékben kapnak időt a futásra. A prioritás értéket a szál Priority attribútumában kell beállítani: tpIdle, tpLowest, tpLower, tpNormal, tpHigher, tpHighest, tpTimeCritical.

Szinkronizáció

 Lehetőségünk van egy szálat elaltatni (Suspend), felébreszteni (Resume), illetve ugyanezt tehetjük a Suspended (Boolean típusú) property állításával. Lehetőségünk van továbbá arra is, hogy egy másik szálat bevárjunk, ezt úgy tehetjük meg, hogy az illető szál WaitFor metódusát meghívjuk. (Ennél a hívásnál persze vigyáznunk kell, nehogy a programunk holtpontra jusson.) Ezek alapján egy példa, amelyben példányosítunk egy szálat, majd megvárjuk, míg befejeződik a végrehajtása.

thread:=TMyThread.Create(false); // elindítjuk is egyben thread.WaitFor; // bevárjuk thread.Free; // felszabadítjuk

 Minden szálnak van egy Terminated logikai tagja, amellyel jelezhetjük a szándékunkat a terminálásra. Nem kényszeríti ki a leállítást, viszont hasznos lehet olyan szálnál, amely ismétlődő feladatot hajt végre végtelen ciklusban, ilyenkor a Terminated jelentheti a megállási feltételt. Meg lehet adni a FreeOnTerminated értékkel, hogy ha lefutott, akkor számolja fel magát a szál. A ReturnValue integer a visszatérő értéke a szálnak (a WaitFor az ebben tárolt értéket adja vissza).

Synchronize metódus

 A TThread osztálynak létezik egy Synchronize metódusa, amelynek segítségével elkerülhetjük a VCL komponensek egyidejű használatából adódó konfliktusokat. Ennek a paramétere egy paraméterekkel nem rendelkező metódus (TThreadMethod).

Lock és Unlock metóduspár

 Szinkronizációra még lehetőség nyílik úgy is, hogy néhány VCL objektumnak van Lock, Unlock metóduspárja. Pl. a TCanvas esetében:

procedure TMyThread.Execute; var x,z: Integer; begin Randomize; //a véletlenszám-generátor inicializálása repeat x := Random(100); y := Random(Form1.ClientHeight); with Form1.Canvas do begin Lock; try Pixels[x,y] := clRed; finally Unlock; end; end; until Terminated; end; procedure FormMouseDown(...) begin Canvas.Lock; try Canvas.Brush := clBlue; finally Canvas.Unlock; end; end;

Kritikus szakaszok (critical section)

 Kritikus szakasznak a forráskód azon részeit nevezzük, melyeket két szál egyidejűleg nem futtathat. Ezek használatával biztosíthatjuk, hogy a program ezen részei egymástól függetlenül hajtódjanak végre. Kritikus szakaszokat csak egy alkalmazáson belül használhatunk.

  A Delphi 3-ban került bevezetésre a VCL részeként a TCriticalSection osztály (a SyncObjs egységben). Ennek segítségével a kritikus szakaszok használata a következő:

var CriticalSection: TCriticalSection;  // TCriticalSection típusú változó deklarálása begin CriticalSection := TCriticalSection.Create; // CriticalSection objektum létrehozása ... CriticalSection.Enter; // belépés a kritikus szakaszba ... // kritikus szakasz CriticalSection.Leave; // kritikus szakasz elhagyása ... CriticalSection.Free; // objektum felszabadítása end;

Mutexek

 Mutexek segítségével különböző erőforrásokhoz való hozzáférést szabályozhatjuk. Az erőforrás használata előtt a mutexet le kell foglalni, majd az erőforrás használata után fel kell engedni azt. A mutex foglalt állapotában egy másik szál nem tudja lefoglalni azt. Meg kell várnia, míg az előző szál felengedi azt. A mutex (a kritikus szakaszokkal ellentétben) megosztható különböző alkalmazások között a neve segítségével.

Példa mutex használatára:

var MyMutex: TMutex; // mutex változó deklarálása begin MyMutex := TMutex.Create(nil, false, ''); // mutex elkészítése ... MyMutex.Acquire; //várakozás egy mutexre (mutex lefoglalása) ... // itt dolgozhatunk az osztott erőforráson MyMutex.Release; // mutex elengedése ... MyMutex.Free; // mutex megsemmisítése end;

Szemafor

 A szemafor a mutexhez hasonló, azonban egyszerre nem csak egy szálnak engedi a hozzáférést. Megadhatjuk például, hogy egy adott erőforráshoz egy időben maximálisan hányan férhetnek hozzá. A mutex lényegében egy olyan szemafor, melynek maximális szálszáma 1.

var MySemaphore: TSemaphore; // szemafor változó deklarálása begin MySemaphore := TSemaphore.Create(nil, 5, 5, ''); // 5 szál számára engedélyezzük az egyidejű használatot ... MySemaphore.Acquire; //várakozás egy szemaforra (lefoglalás) ... // itt dolgozhatunk az osztott erőforráson MySemaphore.Release; // szemafor elengedése ... MySemaphore.Free; // szemafor megsemmisítése end;

Esemény

A szinkronizációs esemény egy olyan objektum, amelyet egy szál kiválthat, így jelezve egy másik szálnak, hogy folytathatja a tevékenységét.

type TMyThread1 = class(TThread) protected procedure Execute; override; end; TMyThread2 = class(TThread) protected procedure Execute; override; end; var MyEvent: TEvent; Thread1: TMyThread1; Thread2: TMyThread2; procedure TMyThread1.Execute; begin ... MyEvent.SetEvent; //kiváltjuk az eseményt ... end; procedure TMyThread2.Execute; begin ... MyEvent.WaitFor(INFINITE); //várunk az esemény bekövetkezésére ... end; begin MyEvent := TEvent.Create(nil, false, false, ''); //létrehozzuk az esemény objektumot Thread1 := TMyThread1.Create(false); Thread2 := TMyThread2.Create(false); ... Thread1.WaitFor; Thread2.WaitFor; MyEvent.Free; //felszabadítjuk az esemény objektumot end;