Umíme to s Delphi, 53. díl – vlákna

Dnes pokračujeme v popisu používání vláken. Nejprve si důkladně popíšeme nejjednodušší prostředek pro práci s vlákny v Delphi – třídu TThread. Pak se vrhneme na nesmírně důležité téma: na synchronizaci vláken.
Vlastnosti třídy TThread

Před týdnem jsme skončili obecným popisem třídy TThread. Dnes začneme tím, že se podrobně zaměříme na vlastnosti této třídy.

Vlastnost: FreeOnTerminate

Popis: Určuje, má-li být objekt vlákna (tedy objekt potomka TThread) automaticky uvolněn z paměti (zrušen), jakmile vlákno skončí svou činnost (doběhne). Pokud nastavíte FreeOnTerminate na False, musíte objekt vlákna explicitně (ručně) zrušit zavolání metody Free!

Vlastnost: Handle

Popis: Vlastnost pouze pro čtení, která obsahuje handle objektu vlákna (jedná se o handle systému Windows, který může být použitelný při manipulování s vláknem prostřednictvím funkcí Windws API).

Vlastnost: Priority

Popis: Určuje prioritu vlákna. Prioritě vláken, jejich určování, nastavování, významu a používání se budeme podrobně věnovat v příštím dílu našeho seriálu.

Vlastnost: ReturnValue

Popis: Specifikuje (celočíselnou) hodnotu, která má být vrácena vláknům čekajícím na dokončení tohoto vlákna. Pomocí vlastnosti ReturnValue je možné indikovat úspěch/chybu, případně předat výsledek aplikaci či jiným vláknům. Metoda WaitFor (ke které se brzy dostaneme) vrací právě hodnotu uloženou ve vlastnosti ReturnValue.

Vlastnost: Suspended

Popis: Říká, je-li vlákno právě pozastaveno. Nastavení hodnoty této vlastnosti na True vede k volání metody Suspend (a tedy k pozastavení vlákna), nastavení na False k volání metody Resume (a tedy k rozběhnutí vlákna). Pozastavené vlákno nepokračuje ve svém provádění (ve své činnosti), dokud není opět rozběhnuto.

Vlastnost: Terminated

Popis: Tato vlastnost je pouze ke čtení a určuje, má-li být vlákno při nejbližší příležitosti ukončeno. Pozor, hodnota této vlastnosti True ještě neznamená, že vlákno bude bezprostředně ukončeno! Věc se má tak: metoda Execute (a všechny případné další metody, které jsou volány metodou Execute) by měla periodicky testovat hodnotu vlastnosti Terminated. V případě, že je tato hodnota rovna True, metoda by měla provádění vlákna ukončit. Ukončení vlákna se pokud možno neprovádí žádnými násilnými příkazy, nejlepší je prostě ukončit běh metody Execute (nechat ji „doběhnout“ ke svému závěrečnému příkazu end). Velmi často se tedy používá smyčka repeat – until, uvnitř které je většina kódu v metodě Execute a která probíhá, dokud je hodnota Terminated = False. Vlákno je samozřejmě možné ukončit i násilně (třeba pomocí funkce Windows API TerminateThread), nicméně výrazně se doporučuje právě používání příznaku Terminated. Je k tomu ovšem nutná součinnost metody Execute, která musí tento příznak testovat a v případě kladné hodnoty ukončit svou činnost (volně provést veškeré „čisticí a závěrečné“ akce a doběhnout). Doběhnutím metody Execute dojde k zastavení a ukončení vlákna. V případě, že hodnota vlastnosti FreeOnTerminate je nastavena na True, dojde i k definitivnímu odstranění objektu vlákna z operační paměti.

Vlastnost Terminated je nastavena na True zavoláním metody Terminate.

S ukončeným vláknem nemůžeme už v zásadě nic užitečného dělat (např. jej znovu spustit apod.) Jediné dvě činnosti, které přicházejí v úvahu, jsou zjištění případné návratové hodnoty vlákna (ReturnValue) a jeho odstranění z paměti (Free).

Vlastnost: ThreadID

Popis: Identifikace vlákna pro systém Windows API. Tato vlastnost je jen pro čtení a je vhodná nejen při volání některých funkcí Windows API pro manipulaci s vlákny, ale také při ladění. Delphi obsahuje docela silný nástroj pro ladění vícevláknových aplikací (později si jej popíšeme podrobně) a při jeho používání je vhodné znát právě hodnotu vlastnosti ThreadID. Pozor, ThreadID je něco jiného než handle vlákna (uchovávaný ve vlastnosti Handle).

Metody třídy TThread

Tolik k vlastnostem třídy TThread. Nyní se podíváme na metody této třídy, neboť i ty se velmi často používají při práci s vlákny.

Metoda: Create

Popis: Konstruktor. Vytváří objekt vlákna (tedy instanci třídy popisující vlákno; instanci našeho potomka třídy TThread). Parametrem konstruktoru je logický údaj říkající, má-li se vlákno vytvořit pozastavené (True), nebo má-li se rovnou spustit (False). Vytvoří-li se pozastavené, musíme jeho provádění spustit ručně zavoláním metody Resume. Konstruktor Create provádí několik inicializačních činností (mezi které patří například inkrementace interního čítače vláken) a na závěr zavolá metodu BeginThread. Touto metodou se budeme také stručně zabývat – jde o způsob, jakým je možné pracovat s vlákny „standardně“, bez použití třídy TThread.

Metoda: Destroy

Popis: Destruktor. Zruší objekt vlákna a uvolní paměť. Nedoporučuje se ovšem volat tento destruktor přímo, lepší je zavolat metodu Free. V případě, že hodnota vlastnosti FreeOnTeminate je nastavena na True, není třeba volat destruktor vůbec, protože vlákno bude po dokončení svého běhu ihned automaticky zrušeno. Pozor! Jestliže při volání destruktoru vlákno dosud běží, ukončí se zavoláním metody Terminate. Pomocí metody WaitFor se počká na úplné dokončení běhu vlákna. Nelze tedy očekávat, že volání destruktoru ihned uvolní vlákno z paměti, protože se prostě musí počkat na to, až vlákno doběhne (z tohoto tvrzení existuje jedna záludná výjimka, povíme si ní níže).

Metoda: DoTerminate

Popis: Generuje událost OnTerminate (v žádném případě tedy neukončuje běh vlákna).

Metoda: Execute

Popis: Tato metoda provádí vlastní činnost vlákna. Ve třídě TThread je definována jako virtuální a abstraktní, její tělo tedy musíme vytvořit sami! Přetížení této metody a implementace programového kódu je nezbytnou podmínkou pro vytvoření vlákna pomocí třídy TThread. Kód napsaný uvnitř metody Execute bude prováděn v době života (běhu) vlákna. Pozor! Metoda Execute je zodpovědná za opakované testování hodnoty vlastnosti Terminated za účelem zjištění, že vlákno má ukončit svůj běh. Pokud metoda zjistí, že Terminated je nastaveno na True, měla by doběhnout a tím zajistit ukončení vlákna. Ukázka této metody:

procedure TCaryVlakno.Execute;
begin
  repeat
    SourX1 := Random(frmHlavni.imgClassic.Width);
    SourY1 := Random(frmHlavni.imgClassic.Height);
    SourX2 := Random(frmHlavni.imgClassic.Width);
    SourY2 := Random(frmHlavni.imgClassic.Height);

    Barva := TColor(Random($FFFFFF));

    Synchronize(ObnovImage);
  until (Terminated = True);
end;

Vlákno (tedy metoda Execute) se začne automaticky provádět ihned po vytvoření (je-li v parametru konstruktoru hodnota False), případně po zavolání metody Resume (v opačném případě). Metodu Execute nikdy nevoláme přímo!

A na závěr jeden veliký pozor: nepoužívejte vlastnosti a metody jiných objektů přímo uvnitř těla metody Execute. Raději vytvořte zvláštní proceduru, která bude k těmto vlastnostem a metodám přistupovat, a zavolejte tuto proceduru zvnitřku metody Execute prostřednictvím metody Synchronize (ukážeme si to u popisu metody Synchronize).

Metoda: Resume

Popis: Obnoví běh pozastaveného vlákna. Metodu Resume zavolejte, pokud potřebujete obnovit běh pozastaveného vlákna. Vlákno se pozastaví voláním metody Suspend, přičemž volání Suspend může být vnořené: abychom běh vlákna obnovili, je nutné zavolat tolikrát Resume, kolikrát bylo předtím zavoláno Suspend.

Metoda: Suspend

Popis: Pozastaví provádění vlákna. Obnovení běhu se provádí zavoláním metody Resume, bližší popis obou metod viz popis metody Resume.

Metoda: Synchronize

Popis: Velmi důležitá metoda. Při jejím volání uvádíme jako parametr adresu metody, která provádí zpravidla nějaké manipulace s objekty knihovny vizuálních komponent (VCL). Zavoláním metody Synchronize vlastně říkáme primárnímu vláknu aplikace, že potřebujeme změnit obsah nějakého vizuálního prvku. Parametr udává proceduru, která ví, jak tuto změnu provést. Primární vlákno aplikace se pak postará o to, aby předaná metoda provedla potřebné změny bezpečně a korektně. Tím, že provádíme modifikace pomocí metody Synchronize, se vyhneme veškerým „vícevláknovým“ konfliktům u VCL, která na více vláken není stavěná. Pokud si nejste jisti, jestli zavolání některé metody je „odolné vůči vícevláknovosti“, zavolejte ji přes primární vlákno – tedy pomocí metody Synchonize.

Zatímco hlavní vlákno aplikace provádí zadanou proceduru, volající vlákno (tedy to, ve kterém jsme volali metodu Synchronize) je pozastaveno.

Použití metody Synchronize není samozřejmě jedinou možností, jak docílit bezpečného chování „nebezpečných“ objektů, nicméně je velmi jednoduché.

Poznámka pro hnidopichy:)

Jsem si vědom toho, že předchozí popis metody Synchronize je trochu napadnutelný z hlediska přesnosti. Ve skutečnosti se nutně nemusí jednat o změnu obsahu nějakého vizuálního prvku. Voláním Synchronize prostě říkáme: "Poslouchej, ty primární vlákno aplikace! Kód, co je uvnitř mojí metody Synchronize, musíš povést právě a jenom ty, abychom si mohli být jisti, že v jednu chvíli bude nad určitými daty (např. komponenty VCL) pracovat pouze jedno vlákno." Je to možná jenom slovíčkaření, ale primární vlákno se postará POUZE o provedení kódu uvnitř metody Synchronize. Změny dat budou provedeny bezpečně jenom proto, že je všechny provede pouze primární vlákno – tedy postupně.

Metoda: Terminate

Popis: Nastaví hodnotu vlastnosti Terminated na True a tím dá vláknu jednoznačně najevo, že by mělo co nejdříve ukončit svůj běh. Metoda Terminate tedy neukončuje běh vlákna! Pokud metoda Execute pravidelně nekontroluje hodnotu vlastnosti Terminated, nebude mít volání metody Terminate žádný patrný účinek, vlákno poběží dál i při opakovaném volání této metody. Možná se divíte, proč je nutné ukončovat vlákno takto složitě, proč jednoduše neexistuje nějaká funkce, jejímž zavoláním by vlákno jednou provždy ukončilo svůj běh. Taková funkce samozřejmě existuje (TerminateThread), nicméně její volání se nedoporučuje, protože vlákna jsou poměrně složitým mechanismem a brutální ukončení některého z nich (aniž by mělo možnost svůj běh nějak korektně ukončit, nastavit potřebné příznaky, vrátit získané výsledky, uvolnit alokované zdroje) způsobí skoro vždy závažné škody a chyby v aplikaci.

Metoda: WaitFor

Popis: Metoda čeká na ukončení běhu vlákna a poté vrátí jako svou návratovou hodnotu hodnotu vlastnosti ReturnValue ukončeného vlákna. Tuto metodu použijte, pokud potřebujete získat výsledek práce některého z vláken. WaitFor ovšem nevrátí žádnou hodnotu, dokud příslušné vlákno neukončí svou činnost! Použití metody WaitFor si ukážeme níže.

Události třídy TThread

Tolik k popisu metod. Zbývá popis událostí – a ten bude mimořádně stručný, protože událost existuje pouze jediná: OnTerminate.

Událost OnTerminate

Popis: Je generována, když skončí provádění (běh) metody Execute – ale ještě předtím, než je vlákno definitivně ukončeno (uvolněno).

Možnosti synchronizace vláken

Nyní, když už dobře známe třídu TThread, můžeme se zabývat problémem, který jsme nastínili v minulém dílu seriálu. Ke slovu se dostává synchronizace vláken. Co to je? K čemu je potřebná?

Minule jsme si ukázali typický problém, který bez synchronizace vláken povede k nesprávným výsledkům. Existuje jedna (globální) proměnná daného procesu, k níž mohou přistupovat obě vlákna. Připomeňme, že vlákna běží vůči sobě předem neznámou, pokaždé jinou rychlostí. Každé z vláken přečte tuto proměnnou, učiní nad ní nějakou operaci a vzápětí zase do proměnné zapíše novou hodnotu. Pokud vlákna nesynchronizujeme, může se snadno stát (a zaručeně se také stane), že systém sebere prvnímu z vláken procesor právě v okamžiku, kdy již provedlo svou operaci, ale ještě nezapsalo správný výsledek. Poté, co druhé vlákno provede celou operaci, první vlákno bude pokračovat v činnosti, zapíše tedy svůj výsledek do proměnné, čímž ale přepíše výsledek práce předchozího vlákna. Podrobný rozbor tohoto problému, i s příkladem, si můžete přečíst v minulém dílu.

Pojďme nejprve vytvořit aplikaci, která bude tento problém demonstrovat prakticky. Pak jej společně vyřešíme a při tom si ukážeme několik možností synchronizace. Vytvoříme si aplikaci, která bude obsahovat dvě vlákna: první přečte hodnotu globální proměnné a zvýší ji o jedničku. Druhé přečte hodnotu globální proměnné a umocní ji na druhou. Naším cílem bude výpočet druhé mocniny z čísla zvětšeného o jedničku (pro hodnotu 5 tedy budeme počítat 6 na druhou). Po spuštění operace si vlákno počítající mocninu nejprve zavolá vlákno, které mu zvýší hodnotu o jedna, pak výsledek umocní a vypíše do nápisu.

(Samozřejmě že tyto operace jsou natolik jednoduché, že nemá smysl vytvářet pro ně ani jedno vlákno, natož hned dvě, ale snad chápete, že nám jde nyní čistě o demonstraci).

1. Vytvořte novou aplikaci, formulář nazvěte frmHlavni a umístěte na něj dvě tlačítka (btnSpocti, btnKonec) a nápis (lblHodnota).

2. V rámci této aplikace vytvořte dvě třídy vláken: TPrictiVlakno a TUmocniVlakno. Obě odvoďte (odděděním) ze třídy TThread. Deklarace tříd budou vypadat takto:

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

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

3. Definujte proměnné pro objekty vláken a také globální proměnnou, se kterou budeme pracovat. Definice připište za definici proměnné hlavního formuláře.

var
  frmHlavni: TfrmHlavni;
  PrictiVlakno: TPrictiVlakno;
  UmocniVlakno: TUmocniVlakno;
  Hodnota: LongInt;

4. Implementujte obě metody Execute a metodu pro obnovení obsahu vizuální komponenty. Vlákno pro mocninu si nejprve vytvoří své vlastní vlákno pro přičtení.

procedure TPrictiVlakno.Execute;
var
  Pomocna: LongInt;

begin
    Pomocna := Hodnota;
    Inc(Pomocna);
    Hodnota := Pomocna;
end;

procedure TUmocniVlakno.Execute;
var
  Pomocna: LongInt;

begin
  PrictiVlakno := TPrictiVlakno.Create(False);

  Pomocna := Hodnota;
  Pomocna := Sqr(LongInt(Pomocna));
  Hodnota := Pomocna;

  Synchronize(ObnovLabel);
  PrictiVlakno.Free;
end;

procedure TUmocniVlakno.ObnovLabel;
begin
  frmHlavni.lblHodnota.Caption := IntToStr(Hodnota);
end;

5. Ošetřete události obou tlačítek. V tlačítku pro výpočet nejprve vytvoříme vlákno (s parametrem konstruktoru False, takže vlákno se ihned spustí) a zařídíme, aby se po skončení svého běhu uvolnilo z paměti.

procedure TfrmHlavni.btnSpoctiClick(Sender: TObject);
begin
  UmocniVlakno := TUmocniVlakno.Create(False);
  UmocniVlakno.FreeOnTerminate := True;
end;

procedure TfrmHlavni.btnKonecClick(Sender: TObject);
begin
  Application.Terminate;
end;

6. Nakonec provedeme potřebné inicializace (a především nastavení hodnoty globální proměnné) v obsluze události OnCreate hlavního formuláře:

procedure TfrmHlavni.FormCreate(Sender: TObject);
begin
  btnKonec.Caption := `&Konec`;
  btnSpocti.Caption := `&Spočti`;
  Self.Caption := `Ukázka synchronizace vláken`;
  lblHodnota.Caption := ``;

  Hodnota := 0;
end;

Poznámka: Vynulování globální proměnné je ve své podstatě zbytečné, protože je automaticky po startu nulována, nicméně víme, že by se mělo ve zdrojovém kódu i přesto vyskytovat.

Aplikace je hotová. Zkusíme nejprve předpovědět několik výsledků a pak je prakticky ověřit. Po klepnutí na tlačítko by se mělo vytvořit vlákno pro přičtení a přičíst jedničku. Ta by se pak měla umocnit – takže výsledek by měl být 1 (při počáteční hodnotě globální proměnné 0). Podobně určíme několik dalších výsledků – viz tabulka:

Pořadí klepnutí Předpokládaný výsledek
První klepnutí (0 + 1)2 = 1
Druhé klepnutí (1 + 1)2 = 4
Třetí klepnutí (4 + 1)2 = 25
Čtvrté klepnutí (25 + 1)2 = 676

Nyní spustíme aplikaci a budeme testovat, zda realita odpovídá těmto předpokladům:

Skutečné, zjištěné hodnoty ukazuje následující tabulka:

Pořadí klepnutí Vypsaný výsledek
První klepnutí 1
Druhé klepnutí 2
Třetí klepnutí 5
Čtvrté klepnutí 26

Vidíte sami, že ke správným výsledkům mají vypočtené hodnoty daleko. Nemůžeme se tedy v žádném případě spolehnout na to, že nějaké vlákno „něco stihne“ předtím, než to budeme potřebovat.

Možná si říkáte, že v tomto případě je to evidentní – přece než se stačí nové vlákno vůbec vytvořit, to běžící již dávno musí stihnout provést umocnění. To je sice pravda, tento příklad je skutečně „do nebe volající“, je však třeba si uvědomit, že to může být právě naopak: nově vytvořené vlákno může provádět velmi krátkou činnost, naproti tomu volající vlákno se pachtí s rozsáhlým výpočtem. A protože nové vlákno skončí dříve, změní v určitém okamžiku hodnotu globální proměnné druhému vláknu „pod rukou“, v průběhu výpočtu.

Toto všechno zdůrazňuji jen proto, že i v případech, kdy můžeme doby výpočtů jednotlivých vláken jasně odhadnout (zdánlivě), musíme používat kvalitní synchronizaci. Jak? Za okamžik si to ukážeme, jen si ještě povíme o jednom častém zdroji chyb.

Uvolní destruktor vlákno hned, nebo až po skončení jeho běhu?

Pokud pracujete v Delphi 6, mají vlákna jednu velmi zajímavou vlastnost. Řekli jsme si, že destruktor (Free) neuvolní vlákno z paměti, dokud neskončí běh jeho metody Execute. To je sice pravda, ale s jednou výjimkou: pokud v okamžiku uvolnění vlákna (tedy v okamžiku volání Free) vlákno ještě nikdy neběželo (nebylo ještě ani jednou ve stavu „běžící“), je okamžitě uvolněno z paměti a nečeká se vůbec na nic – ani na dokončení Execute, ale ani na jeho prvotní spuštění! Pokud tedy zavoláte destruktor tak „brzy“, že systém ještě vláknu ani jednou nedal prostředky (ještě se nerozběhlo), vlákno se vůbec nedostane ke své práci. Proto se může stát, že uvedený program bude po opakovaném klepání na tlačítko Spočti vypisovat stále jen nuly, protože vlákno provádějící přičtení se ani jednou „nestihne“ vůbec spustit. Tato zvláštnost se objevuje při testech v Delphi 6, bez ohledu na verzi Windows (pokud někdo víte ještě o jiné zákonitosti, budu rád, když ji prozradíte v diskusi).

Synchronizace poprvé – čekání na vlákno

První, nejjednodušší možností jak uvedený program „rozchodit“, je tzv. čekání na vlákno. Je evidentní, že v našem případě bychom potřebovali, aby mocnící vlákno se nepustilo do mocnění, dokud neskončí běh vlákna, které přičítá.

K tomu můžeme s výhodou využít metody WaitFor, pomocí které počkáme na skončení běhu „přičítacího“ vlákna. Upravíme tedy kód metody TUmocniVlakno.Execute (viz tučný řádek):

procedure TUmocniVlakno.Execute;
var
  Pomocna: LongInt;

begin
  PrictiVlakno := TPrictiVlakno.Create(False);
  PrictiVlakno.WaitFor;

  Pomocna := Hodnota;
  Pomocna := Sqr(LongInt(Pomocna));
  Hodnota := Pomocna;

  PrictiVlakno.Free;
  Synchronize(ObnovLabel);
end;

Pokud nyní provedeme čtyři klepnutí, budou již výsledky správné:

Pořadí klepnutí Vypsaný výsledek
První klepnutí 1
Druhé klepnutí 4
Třetí klepnutí 25
Čtvrté klepnutí 676

V tomto případě jsme si tedy vystačili s tím, že jedno vlákno počká, až skončí běh druhého. Připomeňme, že pomocí metody WaitFor bychom mohli získat i výsledek běhu vlákna, na které čekáme (používá se k tomu vlastnost ReturnValue, popis viz výše). Opět opakuji, že v tomto případě je využití vláken (která na sebe navíc musejí čekat) zbytečné, ale pro názornost je tento příklad snad dobrý.

Synchronizace podruhé – kritické sekce

Kritické sekce jsou dalším, velmi mocným způsobem synchronizace vláken. Význam pojmu kritická sekce napovídá již sám její název. Označíme-li nějaký úsek programového kódu za kritickou sekci, zajistíme, že pokud jej začne jedno vlákno provádět, nebude ve své činnosti „rušeno jinými vlákny“ až do okamžiku, kdy z kritické sekce vystoupí (kdy ji opustí). Úsek definovaný jako kritická sekce je tak bezpečný vůči vláknům, protože operační systém zajistí, že do něj může v jednom okamžiku přistupovat vždy pouze jedno vlákno.

Kritické sekce se obvykle používají k zajištění sériového přístupu k nějakému globálnímu zdroji, např. ke globální proměnné. Pokud zajistíme, že k dané datové struktuře (proměnné) bude muset přistupovat pěkně jedno vlákno po druhém, nedostaneme se do problémů způsobených s tím,že by jedno vlákno změnilo nějaká globální data jinému vláknu „pod rukou“.

Používání kritických sekcí je poměrně jednoduché a navíc relativně rychlé. Pokud je kritická sekce volná, nezpůsobí vstup do této sekce skoro žádné zpomalení výpočtu. Mechanismus kritických sekcí ze systémového hlediska funguje zhruba takto:

Vlákno A pracuje a vstoupí do kritické sekce označené CS (říkáme také, že si uzamkne kritickou sekci CS). V okamžiku, kdy ji uvnitř této kritické sekce, provede operační systém přepnutí na vlákno B. To chvíli pracuje a pak by rádo vstoupilo do téže kritické sekce CS. Operační systém však zjistí, že v kritické sekci CS je právě vlákno A, proto pozastaví vlákno B a zároveň si zapamatuje, že vlákno B čeká na vstup do kritické sekce CS. Poté, co se vlákno A dostane opět „ke slovu“, dokončí operaci uvnitř kritické sekce. Operační sytém si nyní „vzpomene“, že na vstup do této kritické sekce čeká vlákno B, přepne tedy na něj a operace pokračuje.

Jedním z problémů kritických sekcí je však skutečnost, že neexistuje žádný časový (ani jiný) limit pro dobu setrvání v kritické sekci. Pokud nějaké vlákno vstoupí do kritické sekce a odmítá z ní vystoupit, čekající vlákna se do téže kritické sekce prostě nedostanou a může dojít k zablokování celé aplikace.

Ukážeme si nyní nejjednodušší způsob použití kritické sekce. Aby bylo možné tento synchronizační mechanismus využívat, je nutné vytvořit globální objekt třídy TCriticalSection (objekt musí být globální, aby byl viditelný ze všech vláken). Třída TCriticalSection je deklarována v modulu SyncObjs, tento modul je tedy nutné přidat do sekce Uses našeho modulu! Třída TCriticalSection má (kromě jiného) dvě důležité metody – Acquire (která zablokuje ostatním vláknům vstup do kritické sekce) a Release (která odstraní tento blok).

Každá kritická sekce, kterou vytvoříme, je asociována s nějakým globálním objektem, který chceme chránit. Každé z vláken, které přistupuje do globální paměti, by tedy mělo nejprve zavolat metodu Acquire, aby zajistilo, že žádné jiné vlákno nebude současně přistupovat k témuž objektu. Když dokončí svou operaci, musí co nejdříve zavolat Release, aby ostatní vlákna nebyla zbytečně blokována.

Pozor: tyto kritické sekce pracují pouze v případě, že je používá opravdu každé vlákno přistupující ke globálním objektům. Vlákna, která budou přistupovat k objektům bez ověření metodou Acquire, se k těmto objektům normálně dostanou a zcela devalvují význam kritických sekcí.

Za týden

Tolik tedy k dnešní náplni seriálu. Je vidět, že vlákna jsou opravdu velmi rozsáhlým tématem a že jejich programování přináší celou řadu úskalí a „zrad“. Je stále třeba mít na paměti, že vlákna si běží na sobě nezávisle, že nemáme zaručenu ani rychlost jejich provádění, ani pořadí. S touto „nevyzpytatelností“ je nutno počítat a vícevláknové programy stavět co možná nejrobustnější. Kromě toho je nutné vlákna pořádně synchronizovat. Dva základní synchronizační mechanismy jsme si ukázali dnes a za týden budeme pokračovat – nejprve si ukážeme praktické použití kritických sekcí a pak si popíšeme další možnosti synchronizace. Dále se budeme podrobně věnovat prioritě vláken a jejímu nastavování.
Váš názor Další článek: Papírový výstup z digitálních fotografií

Témata článku: , , , , , , , , , , , , , , , , , , , , , , , , ,