Többszálú programok delphi belülről

Többszálú programok a Delphi belülről

A többszálú alkalmazás kifejlesztésének folyamata során két egymással összefüggő problémát kell megoldani: az erőforrásokhoz való hozzáférést és a holtpontot. Ha több szál ugyanazt az erőforrást (memória területet, fájlt, eszközt) a gondatlan programozással érinti, akkor előfordulhat, hogy több szál megpróbál valamilyen manipulációt végrehajtani a megosztott erőforrással. Ebben az esetben az erőforrás elérésének normális sorozata valószínűleg sérül. A hozzáférési elhatárolás problémája igen egyszerű műveletekkel is felmerülhet. Tegyük fel, hogy van olyan programunk, amely több szálat hoz létre. Minden szál elvégzi a feladatot, majd elvégzi a feladatot. Az adott időpontban aktív szálak számát szeretnénk szabályozni, és erre a célra belépünk a szálszámlálóba - a globális változó Counter-be. Az áramlás folyamata így néz ki: a MyThread.Execute eljárás;






kezdődik
Inc (számláló);
.
Dec (számláló);
végén;

Az egyik probléma, amely ezt a kódot, hogy az eljárások Inc és december nem atomi (például eljárást Inc elvégzéséhez szükséges három processzor utasítás - Load értékén nyilvántartást a processzor, hogy végre növekmény magát, majd beírjuk az eredményt a processzor regiszterek memóriaterület counter). Könnyen elképzelhető, hogy ha a két szál megpróbál egyszerre elvégezni az eljárást Inc, Counter-érték növelhető 1 2 helyett ilyen hiba nehéz kimutatni, amikor hibakeresés, mivel a hiba valószínűsége nem egyenlő az egységet. A hullám megtörténhet, hogy a program tesztelésének ideje alatt minden rendben lesz, és az ügyfél már megtalálja a hibát.

A probléma megoldása lehet speciális funkciók használata. Például az Inc eljárás helyett a Win API InterlockedIncrement eljárással és a Dec-InterlockedDecrement helyett használhatja. Ezek az eljárások biztosítják, hogy egyidejűleg legfeljebb egy szál férhessen hozzá a számlálóváltozóhoz.

Általánosságban elmondható, hogy a blokkolás a hozzáférés megosztásának problémáját oldja meg, speciális mechanizmusok, amelyek biztosítják, hogy minden egyes időben csak egy szál fér hozzá bizonyos erőforrásokhoz. Ha egy erőforrás egy szálat blokkol, az erőforrás elérésére próbálkozó egyéb szálak végrehajtása felfüggesztésre kerül. Azonban a zárak használata további problémát okoz - holtpontok. A patthelyzet fordul elő, amikor a patak egy erőforrás blokkok szükséges folytatni a munkát patak B és folyam B blokkokat erőforrás szükséges folyamatos munkafolyamat A. Mivel nincs menet továbbra is végrehajtási zárolt forrásokat nem lehet kiadni, ami lefagy vagy patakok.

Szálak Delphiban 6

A vizsgált megvalósítások közül a Delphi 6 szálak végrehajtása a legegyszerűbb. Ez az alap, amelyen a későbbi verziók komplex áramlási folyamatok komplex modelljét képezik.

A Delphi 6, valamint más változatai Delphi, mint az áramlás a funkció használható a funkciója ThreadProc osztályba modul, ami viszont a tárgy TThread Execute módszer: function ThreadProc (Szál: TThread): egész;
var
FreeThread: logikai;
kezdődik
megpróbál
ha nem Thread.Terminated, akkor
megpróbál
Thread.Execute;
kivéve
Thread.FFatalException: = AcquireExceptionObject;
végén;
végül
FreeThread: = Thread.FFreeOnTerminate;
Eredmény: = Thread.FReturnValue;
Thread.FFinished: = Igaz;
Thread.DoTerminate;
ha a FreeThread majd Thread.Free;
EndThread (eredmény);
végén;
végén;

Fontos hangsúlyozni, hogy a szinkronizálása módszer nem csak szinkronizálja a végrehajtás átadott eljárás módszert a más módszerekkel a fő stream, hanem végrehajtásának megszervezéséhez a módszer keretében a fő téma. Azaz, például a globális változókat nyilvánították threadvar, amikor a módszer értelmezése a hozzájuk rendelt a fő stream, de nem azok, amelyek a rájuk bízott az áramlás okozta szinkronizálása.

A szinkronizálás nem elegendő a szinkronizálási módszer fő és másodlagos szálainak szokásos szinkronizálásához. Képzeld el ezt a helyzetet: a módszer a fő stream, várjuk a befejezését a másodlagos áramlást (nem számít, milyen módon, milyen fontos, hogy ez a módszer nem tér vissza a vezérlést a fő áramlási amíg a másodlagos adatfolyam teljes), és egy másodlagos áramlást ebben az időben A módszer szinkronizálása. Ennek eredményeként, a holtpont esetén: fő áramlási módszert nem lehet befejezni, ha a másodlagos áramlási befejeződött, és a szekunder közeg nem lehet befejezni, amíg szinkronizálása módszer kerül sor (és erre a célra, a fő áramlási visszatért az üzenet feldolgozása ciklus). Ahhoz, hogy e helyzet megoldására, ott CheckSynchronize funkció, ami egy hívás, hogy hajtsák végre az összes módszert, amelyek jelenleg SyncList sorban, és ellenőrizzék vissza az összes szinkronizálása módszer, az úgynevezett másodlagos áramlást. A Delphi 6-ban ez a függvény a következőképpen működik: function CheckSynchronize: Boolean;
var
SyncProc: PSyncProc;
kezdődik
ha GetCurrentThreadID <> Ezután a MainThreadID
EThread.CreateResFmt emelés (@SCheckSynchronizeError, [GetCurrentThreadID]);
ha a ProcPosted akkor
kezdődik
EnterCriticalSection (ThreadLock);
megpróbál
Eredmény: = (SyncList <> nil) és (SyncList.Count> 0);
ha Eredmény akkor
kezdődik
míg a SyncList.Count> 0 nem
kezdődik
SyncProc: = SyncList [0];
SyncList.Delete (0);
megpróbál
SyncProc.Thread.FMethod;
kivéve
SyncProc.Thread.FSynchronizeException: = AcquireExceptionObject;






végén;
SetEvent (SyncProc.signal);
végén;
ProcPosted: = hamis;
végén;
végül
LeaveCriticalSection (ThreadLock);
végén;
end else Eredmény: = hamis;
végén;

Mint látható, CheckSynchronize funkció egyszerű: ellenőrzi az értéke ProcPosted, és ha ez az érték igaz, akkor következetesen eltávolítja feljegyzések a sorból SyncList elvégzi a megfelelő módszerek és létrehozza a megfelelő jeleket. Vegye figyelembe, hogy a függvény ellenőrzi (a GetCurrentThreadID használatával), hogy melyik szálat hívja. A CheckSynchronize hívása nem a fő szálból káoszhoz vezethet, így ebben az esetben egy EThread kivételt dobunk. A szinkronizálással a fő téma által "beépített" módú hívások kezelésére a fő szál-üzenet feldolgozási ciklus a CheckSynchronize módszert is felhívja.

Egy másik érdekes módszer számunkra a TThread osztály WaitFor módszere. Ez a módszer megakadályozza a szál végrehajtását, amelyik azt okozta, amíg a WaitFor objektumhoz tartozó szál meg nem szűnik. Egyszerűen fogalmazva, ha a fő szálon a MyThread stream befejezését várjuk, hívhatjuk a MyThread.WaitFor; Így működik a WaitFor a Delphi 6 alkalmazásban: a TThread funkció. WaitFor: LongWord;
var
H: THandle;
WaitResult: bíboros;
Üzenet: TMsg;
kezdődik
H: = FHandle;
ha GetCurrentThreadID = MainThreadID akkor
kezdődik
WaitResult: = 0;
ismétlés
ha WaitResult = WAIT_OBJECT_0 + 1 majd
PeekMessage (Msg, 0, 0, 0, PM_NOREMOVE);
Alvás (0);
CheckSynchronize;
WaitResult: = MsgWaitForMultipleObjects (1, H, hamis, 0, QS_SENDMESSAGE);
Win32Check (WaitResult <> WAIT_FAILED);
amíg WaitResult = WAIT_OBJECT_0;
end else WaitForSingleObject (H, INFINITE);
CheckThreadError (GetExitCodeThread (H, Eredmény));
végén; A WaitFor módszer nemcsak a fő szálon, hanem bármelyik másként is hívható. Ha waitfor hívják a fő szál GetCurrentThreadID = MainThreadID az eljárás időnként okoz CheckSynchronize révén megakadályozzák a fenti patthelyzet, valamint rendszeresen kiüríti az összes Windows-üzeneteket. Ha waitfor metódus bármely más patak, amely állítólag a saját üzenetsort nem lehet, ő csak arra vár, hogy a jel a végén az áramlás-vezérelt használatával Win API WaitForSingleObject. Itt meg kell jegyezni, hogy az adatfolyam azonosító (FHandle mező) egy részmunkaidős jel, amely be van állítva a Windows, amikor leállt az áramlás.

És mi történik, ha a szál magára hívja a WaitFort? A szál, amely maga okozta a Self.WaitFor; örökre várakozik a saját befejezésére (legalábbis addig, amíg egy másik szál a "kemény" Win API TerminateThread függvényt nem hívja). Természetesen nem valószínű, hogy egy értelmes programozó írna valamit, mint az Self.WaitFor, de a helyzetek bonyolultabbak lehetnek. Furcsa, hogy a Delphi fejlesztők nem ilyen lehetőség megelőzésére, „öngyilkos”, és valójában, hogy nagyon egyszerű - csak összehasonlítani az értéket FHandle és a visszatérési értéke GetCurrentThreadID.

Szálak Delphiban 7

A Delphi 6-hoz képest a Delphi 7-ben nem sok változás érhető el a szálakkal való munka során. Fontolja meg a CheckSynchronize funkció végrehajtását: CheckSynchronize funkció (Timeout: Integer = 0): Boolean;
var
SyncProc: PSyncProc;
LocalSyncList: TList;
kezdődik
ha GetCurrentThreadID <> Ezután a MainThreadID
EThread.CreateResFmt emelés (@SCheckSynchronizeError, [GetCurrentThreadID]);
ha a Timeout> 0 majd
WaitForSyncEvent (Timeout)
más
ResetSyncEvent;
LocalSyncList: = nulla;
EnterCriticalSection (ThreadLock);
megpróbál
Integer (LocalSyncList): = InterlockedExchange (Integer (SyncList), Integer (LocalSyncList));
megpróbál
Eredmény: = (LocalSyncList <> nil) és (LocalSyncList.Count> 0);
ha Eredmény akkor
kezdődik
míg a LocalSyncList.Count> 0 nem
kezdődik
SyncProc: = LocalSyncList [0];
LocalSyncList.Delete (0);
LeaveCriticalSection (ThreadLock);
megpróbál
megpróbál
SyncProc.SyncRec.FMethod;
kivéve
SyncProc.SyncRec.FSynchronizeException: = AcquireExceptionObject;
végén;
végül
EnterCriticalSection (ThreadLock);
végén;
SetEvent (SyncProc.signal);
végén;
végén;
végül
LocalSyncList.Free;
végén;
végül
LeaveCriticalSection (ThreadLock);
végén;
végén;

Az új változat helyett ProcPosted zászló felhasználhatja SyncEvent esemény kezelése, amely egy sor olyan funkciók: SetSyncEvent, ResetSyncEvent, WaitForSyncEvent. A WaitFor módszer a SyncEvent eseményt használja az üzenethurok optimalizálására. A SyncEvent telepítése azt jelzi, hogy egy új módszer várakozik a sorban, várva a szinkronizálást, és ellenőriznie kell a CheckSynchronize szolgáltatást.

A CheckSynchronize módszernek van egy TimeOut paramétere, amely meghatározza, hogy a módszer mennyi ideig várjon a SyncEvent eseményre, mielőtt visszatenné a vezérlést. Meghatározza timeout kényelmes ahol CheckSynchronize metódus egy hurokban (a menet, hogy felhívja CheckSynchronize, így a CPU időt más szálak helyett csavaró kihívások tétlen), de az időtartam és CheckSynchronize eljárás hívás szükségtelenül növelik. Ügyeljen arra is, hogy a SyncList sorral végzett munka megváltozott a Delphi 7-ben. A korábbi verziók összes CheckSynchronize SyncList rögzíti (a ThreadLock) idejére feldolgozás várakozó módszerek (és ebben az időben is viszonylag nagy). Bár a CheckSynchronize tulajdonában van a SyncList objektum, a SyncList sorból más szálakból végrehajtott műveletek blokkolva vannak. Annak érdekében, hogy engedje el a SyncList a lehető leghamarabb, megtartja egy mutatót az aktuális várakozási sor tárgy (a Win API InterlockedExchange funkció) egy helyi változó LocalSyncList, és változó SyncList beállítja nullára. Ezt követően újra megjelenik a SyncList változóhoz való hozzáférés. Most, ha egy másik szál akar újra szinkronizálni módszer, akkor létre kell hozni egy új objektumot SyncList, de a hozzáférést a sorban csak blokkolni kell a szükséges időt csere mutatók, így az általános termelékenységet kellene jelentős.

A módszer a munka a blokkoló mód hasonlít a munka szinkronizálása módszer Delphi 7: A rendszer létrehoz egy esemény SyncProc.Signal, amely jelzi a végrehajtását a módszer a fő téma, majd képezi a szerkezet SyncProc leíró szinkronizált módszer egészíti ezt a struktúrát SyncList viszont meghatározza a jel SyncEvent és vár CheckSynchronize funkció nem indul el a riasztást SyncProc.Signal, jelezve, hogy a szinkronizált módszer kerül végrehajtásra. A leírás, az úgynevezett módszer még ma is használják rekord típusú TSyncProc, amely azonban másképp néz ki: TSyncProc = rekord
SyncRec: PSynchronizeRecord;
Queued: Boolean;
Jel: THandle;
végén;

A SyncRec mező egy mutató a TSynchronizeRecord struktúrához. A Queued mező azt jelzi, hogy a hívás aszinkron, és a Signal mezőt használják a hívás blokkolásakor.

Ha QueueEvent átadott paramétert Igaz, a módszer hívás bekerül a sorban aszinkron. Ebben az esetben mi létrehozunk egy új TSyncProc felvétel (aszinkron hívás nem használható lokális változó, hiszen a struktúra létezik befejezése után a hívás szinkronizálása).

A Delphi menetvágásának hátrányai

A legfontosabb hátrány az áramlás-végrehajtás felfüggesztésére és folytatására alkalmazott módszer. E célból a VCL a SuspendThread és a ResumeThread Windows API-kat használja, amelyek általánosságban szólnak. Hibakeresési célokra tervezték. A SuspendThread funkció bármikor leállíthatja a menet végrehajtását. A szál nem tilthatja meg a felfüggesztést egy kritikus kód végrehajtására, és nem kap figyelmeztetést, hogy felfüggesztik. A másodlagos szálak és a fő szál közötti üzenetek cseréje jól átgondolt, a Delphi legújabb verzióiban még az aszinkron hívások is hozzáadódnak, de nincsenek szabványos mechanizmusok az üzenetek átvitelére a fő szálról a másodlagos szálakra. Itt meg kell jegyeznünk, hogy a "fő szál" alatt azt a szálat értjük, amelyben az Application.Run módszert végrehajtjuk, és az eseményeket feldolgozzuk. A Delphi rossz egy modell számára, amelyben minden szál egyenlő.

felhatalmazás




Kapcsolódó cikkek