Többszálú programok Delphi belül - szoftverek

A folyamat során a fejlődő többszálú alkalmazás megoldására két, egymással összefüggő problémák - szétválasztása erőforrásokhoz való hozzáférés és a holtpont. Ha több szálon kapcsolódnak ugyanarra a forrás (memória területet, fájl, eszköz) gondatlan programozás helyzet állhat elő, amikor több szál próbál végrehajtani néhány manipuláció a megosztott erőforrás. Ebben az esetben a normál műveletek sorozata elérésekor az erőforrás valószínűleg eltört. A probléma a differenciálódás hozzáférési száma még akkor is nagyon egyszerű művelet. Tegyük fel, hogy van egy program, amely létrehoz több szálat. Minden szál látja el feladatát, majd kilép. Azt akarjuk, hogy ellenőrizzék a szálak száma, amelyek aktívak egy adott időpontban, és e célból bemutatjuk az áramlásmérő - egy globális változót Counter. Flow eljárás így néz ki, mint: eljárás MyThread.Execute;
kezdődik
Inc (Counter);
.
Dec (Counter);
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. Hullám előfordulhat, hogy a próbaüzem valamennyi program fog működni, és a hiba talált egy ügyfél.

A megoldás lehet a speciális funkciókat. Például ahelyett, hogy az eljárás Inc használhatja Win API InterlockedIncrement eljárás, de ahelyett, hogy december - InterlockedDecrement. Ezek az eljárások biztosítják, hogy bármikor nem több mint egy szál van hozzáférése a számláló változó.

Általában a probléma megoldása érdekében az elválasztás segítségével a hozzáférést lock - különleges mechanizmusokat, amelyek biztosítják, hogy egy adott időpontban csak egy szál fér hozzá egy bizonyos erőforrás. Abban az időben, amikor az erőforrás van zárva egy szál, más szálak próbál hozzáférni egy erőforrást, fel van függesztve. Azonban az zárak további problémát okoz - holtpont (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.

Folyóvizeinek Delphi 6

Az összes implementáció, a végrehajtás áramlás Delphi 6 a legegyszerűbb. Olyan, mint egy alapot, amelyre később építeni egy komplex modell interakció folyik.

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: Boolean;
kezdődik
megpróbál
ha nem, akkor Thread.Terminated
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: = True;
Thread.DoTerminate;
ha FreeThread majd Thread.Free;
EndThread (Eredmény);
végén;
végén;

TSyncProc szerkezet formájában: TSyncProc = rekord
Téma: TThread;
Jel: THandle;
végén;

Információkat tartalmaz az áramlás okozta szinkronizálása, és egy jel azonosítására használt szinkronizálni szinkronizálása (bocsánat a kényszerű szójáték).

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 megfelelő szinkronizálás a fő és a másodlagos áramlási szinkronizálása módszer nem elegendő. 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 szinkronizálása módszer. 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, ez a funkció van megvalósítva a következőképpen: funkció CheckSynchronize: Boole;
var
SyncProc: PSyncProc;
kezdődik
ha getCurrentThreadID <> MainThreadID majd
emelni EThread.CreateResFmt (@SCheckSynchronizeError, [GetCurrentThreadID]);
ha ProcPosted majd
kezdődik
EnterCriticalSection (ThreadLock);
megpróbál
Eredmény: = (SyncList <> nil) és (SyncList.Count> 0);
ha Eredmény majd
kezdődik
míg SyncList.Count> 0 do
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: = false;
végén;
végül
LeaveCriticalSection (ThreadLock);
végén;
végén pedig Eredmény: = false;
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. Megjegyezzük, hogy a funkció ellenőrzi (a GetCurrentThreadID), amelyből ez okozta az áramlást. Hívjon CheckSynchronize nem a fő áramlási vezethet káosz, úgyhogy ebben az esetben keletkezik EThread kivétel. Feldolgozásra metódushívások „beágyazott” a fő stream a szinkronizálás, a feldolgozási ciklus a fő szál üzenete is okoz CheckSynchronize módszer.

Egy másik érdekes módszer számunkra - az eljárás waitfor osztály TThread. Ez a módszer blokkolja végrehajtását hívó szálat, amíg, befejezéséig az áramlás számára, amely az objektum hívták waitfor. Egyszerűen fogalmazva, ha a fő téma akarunk várni a menet megszüntetésére MyThread nevezhetjük MyThread.WaitFor;

Itt van, hogyan waitfor végrehajtani Delphi 6: function TThread.WaitFor: LongWord;
var
H: THandle;
WaitResult: Cardinal;
Msg: TMsg;
kezdődik
H: = FHandle;
ha GetCurrentThreadID = MainThreadID majd
kezdődik
WaitResult: = 0;
ismétlés
ha WaitResult = WAIT_OBJECT_0 + 1, akkor
PeekMessage (MSG, 0, 0, 0, PM_NOREMOVE);
Sleep (0);
CheckSynchronize;
WaitResult: = MsgWaitForMultipleObjects (1, H, False, 0, QS_SENDMESSAGE);
Win32Check (WaitResult <> WAIT_FAILED);
amíg WaitResult = WAIT_OBJECT_0;
véget mást WaitForSingleObject (H, INFINITE);
CheckThreadError (GetExitCodeThread (H, Eredmény));
végén;

Waitfor módszer lehet nevezni nem csak a fő téma, hanem bármely más. 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 az áramlás hatására waitfor magának? Flux okozott Self.WaitFor;

mindig számíthat a befejezése saját (legalábbis addig, amíg egy másik szál kéri a „kemény” Win API TerminateThread funkció egy adott áramlás). Természetesen alig épeszű programozó írni valami ilyesmit Self.WaitFor, de a helyzet sokkal bonyolultabb lehet. 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.

Patakok a Delphi 7

Összehasonlítva Delphi 6 változása dolgozó szálak a Delphi 7 nem annyira. Tekintsük a végrehajtás CheckSynchronize funkció: function CheckSynchronize (időtúllépés: Integer = 0): Boole;
var
SyncProc: PSyncProc;
LocalSyncList: TList;
kezdődik
ha getCurrentThreadID <> MainThreadID majd
emelni EThread.CreateResFmt (@SCheckSynchronizeError, [GetCurrentThreadID]);
ha időtúllépés> 0, akkor
WaitForSyncEvent (időtúllépés)
más
ResetSyncEvent;
LocalSyncList: = nil;
EnterCriticalSection (ThreadLock);
megpróbál
Egész szám (LocalSyncList): = InterlockedExchange (Egész szám (SyncList), Integer (LocalSyncList));
megpróbál
Eredmény: = (LocalSyncList <> nil) és (LocalSyncList.Count> 0);
ha Eredmény majd
kezdődik
míg LocalSyncList.Count> 0 do
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. Waitfor módszer SyncEvent esemény optimalizálására üzenet feldolgozása során. Telepítése SyncEvent azt jelzi, hogy viszont egy új módszer, hogy várja a szinkronizálást és a hívni kívánt CheckSynchronize.

A CheckSynchronize módszer megjelent timeout paraméter, ami azt jelzi, hogy milyen hosszú a módszert kell várni SyncEvent eseményeket, mielőtt visszatért ellenőrzés. 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. Figyelemreméltó az is, ahogyan Delphi 7 megváltozott dolgozni SyncList sorban. 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). De amíg CheckSynchronize tulajdonosa tárgy SyncList műveletek SyncList sorban, a többi szál 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 hozzáférést változó SyncList újra kinyit. 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;
Várólistán: Boolean;
Jel: THandle;
végén;

SyncRec egy mutató, hogy egy szerkezetet TSynchronizeRecord. Sorban álló mező jelzi, hogy a hívás aszinkron, és Signal mező használható blokkolja a hívást.

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).

Hátrányai menet végrehajtása Delphi

A legnagyobb hátránya kell ismerni módszer, hogy függessze fel, és folytassa a szál végrehajtását. Ebből a célból a VCL használt Windows API SuspendThread funkció és ResumeThread, ami általánosságban. Szánt hibakeresés. SuspendThread funkció megállítani az áramlást bármely pontján. Flow nem tilthatják meg a felfüggesztés időtartama alatt a kritikus darab kódot, és nem kap értesítést, hogy fel kell függeszteni. Közötti üzenetváltás a szekunder áramlás és a fő áramlási a gondolat révén elég jól az utóbbi változat a Delphi még aszinkron hívások hozzá, de szabványos üzenetek mechanizmusát a fő áramlási a másodlagos nem létezik. Itt meg kell jegyezni, hogy a „main stream” megértjük az áramlás, azaz Application.Run módszer, és a feldolgozott eseményeket. Delphi rosszul alkalmas modell, amelyben az összes áramok egyenlő.

felhatalmazás

de ez nem működik, a fő stream. Szinkronizálása módszer ami Queue, ellenőrizze, hogy nem hívják a fő áramlási és ebben az esetben végre MethodToExecute, késedelem nélkül. Tehát ExecAfter eljárás: eljárás ExecAfter (AMethod TThreadMethod.);
var
SyncProcPtr: PSyncProc;
SyncRecPtr: PSynchronizeRecord;
kezdődik
Új (SyncProcPtr);
Új (SyncRecPtr);
SyncRecPtr.FThread: = nil;
SyncRecPtr.FMethod: = AMethod;
SyncRecPtr.FProcedure: = nil;
SyncRecPtr.FSynchronizeException: = nil;
SyncProcPtr.Signal: = 0;
EnterCriticalSection (ThreadLock);
megpróbál
SyncProcPtr.Queued: = true;
ha SyncList = nil majd
SyncList: = TList.Create;
SyncProcPtr.SyncRec: = SyncRecPtr;
SyncList.Add (SyncProcPtr);
SignalSyncEvent;
ha Címzett (WakeMainThread), majd
WakeMainThread (SyncProcPtr.SyncRec.FThread);
végül
LeaveCriticalSection (ThreadLock);
végén;
végén;

A verzió lehetővé teszi, hogy a hívás ExecAfter AMethod módszer megjelenése után ez az eljárás, melynek során úgynevezett ExecAfter (ha szükséges könnyen átírni eljárás hívás független eljárások és módszerek egyike sem). Végrehajtás ExecAfter eljárásokat kell elhelyezni az osztályba egység, különben nem tud hozzáférni a szükséges változók ThreadLock és SyncList. Egyébként, ha nem akarja megváltoztatni az „elsődleges” példánya osztályba, akkor egy helyi másolatot egy adott program. Másolásához Classes.pas módosított fájlt a projekt könyvtárban. Most, ha hozzá a program-sorozat: TForm1.Method1;
kezdődik
ExecAfter (method2);
metódus 3;
végén;

Method2 akkor végezzük, amikor a metódus 3 (és elhagyása után a method1).

Kapcsolódó cikkek