Autor Téma: Start náročné procedury ve vláknu + okamžité zrušení v případě potřeby  (Přečteno 1258 krát)

Offline vandrovnik

  • Guru
  • *****
  • Příspěvků: 1247
  • Karma: 51
    • Verze Delphi: 10.3
Měl bych dotaz k té critical section. Když do ní vstoupím (v Execute části běžícího vlákna) a uvnitř zároveň zavolám Synchronize, může zamrznout aplikace? Přijde mi to logické, kdy na začátku kritické sekce se pozastaví zbývající vlákna a tudíž Synchronize na pozastaveném vláknu nevyvolá činnost a aplikace zde zůstane "trčet" nekonečně dlouho. Je to tak?

Myslím, že je to tak, jak píšeš. Synchronize bych volal mimo tu kritickou sekci.

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 3280
  • Karma: 139
    • Verze Delphi: D2007, XE3, DX10
Měl bych dotaz k té critical section. Když do ní vstoupím (v Execute části běžícího vlákna) a uvnitř zároveň zavolám Synchronize
Tak to je to nejhorsi, co muzes udelat. Z principu: kriticke sekce se pouzivaji k zajisteni vylucneho pristupu ke sdilenym prostredkum v paralelnim programovani a obecne plati zasada, ze cas straveny v kriticke sekci se minimalizuje.

Citace
může zamrznout aplikace? Přijde mi to logické, kdy na začátku kritické sekce se pozastaví zbývající vlákna a tudíž Synchronize na pozastaveném vláknu nevyvolá činnost a aplikace zde zůstane "trčet" nekonečně dlouho. Je to tak?
Kdyz je to spatne pouzite, tak zamrznout muze: zbyvajici vlakna se nepozastavuji - jen ta, ktera chteji vstoupit do stejne kriticke sekce. Vlastni Synchronize udela jen to, ze strci metodu do fronty pozadavku, ktere se maji vykonat v kontextu hlavniho threadu a tomu da zaslanim windows zpravy na vedomi, ze neco v te fronte ma. Thread sam pak ceka na stav signaled u event, ze uz to hlavni thread vyridil. Cekani se lze vyhnout pouzitim volani Queue misto Synchronize.

Takze jestli dochazi k deadlocku, tak jen proto, ze hlavni thread at uz primo nebo neprimo zkousi vstoupit do dane kriticke sekce...

##

Jinak novejsi Delphi uz nejaky rok obsahuje System.TMonitor, coz je zejmena z pohledu udrzovatelnosti a srozumitelnosti kodu prostredek, jak nahradit platformove zavisle kriticke sekce. A o monitorech v paralelnim programovani ucili uz me :-)

« Poslední změna: 22-09-2021, 12:20:53 od pf1957 »

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 309
  • Karma: 0
U nás na škole byl učitel o jednu lekci před námi. Navíc tíhli k BASICu a ASSEMBLERu :(

Je tedy Queue v tomto směru "bezpečnější"?

Když ve vláknu pracuji se souborem, například přes TFileStream, je správné ohraničit od TFileStream.Create až po TFileStream.Free kritickou sekcí?

Děkuji.

Offline vandrovnik

  • Guru
  • *****
  • Příspěvků: 1247
  • Karma: 51
    • Verze Delphi: 10.3
Když ve vláknu pracuji se souborem, například přes TFileStream, je správné ohraničit od TFileStream.Create až po TFileStream.Free kritickou sekcí?

(Např.) kritická sekce se používá tehdy, když s něčím může pracovat více vláken a Ty chceš zajistit, aby v danou chvíli mělo přístup jen jedno z nich (ostatní, která to zkusí, budou čekat u "vstupu" do té kritické sekce).

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 309
  • Karma: 0
Dobře, a jak se správně inicializuje kritická sekce? V mnoha příkladech na internetu je možné nalézt "InitializeCriticalSection" v Create části potomka TThread. To znamená, že každá instance vlákna "má svoji kritickou sekci". Není to hloupost? Neměli by mít všechny vlákna jednu společnou "TRTLCriticalSection" vytvořenou mimo potomky TThread? Takové příklady totiž taky existují, kdy je "InitializeCriticalSection" a "DeleteCriticalSection" voláno globálně mimo vlákna a "TRTLCriticalSection" je globální proměnná mimo vlákna. Snad jsem to popsal srozumitelně.

Děkuji.


Beru zpět, na docwiki.embarcadero.com jsem našel odpověď. Samozřejmě to mám v kódu trochu jinak.
« Poslední změna: 22-09-2021, 13:44:56 od age.new »

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 3280
  • Karma: 139
    • Verze Delphi: D2007, XE3, DX10
Je tedy Queue v tomto směru "bezpečnější"?
Takhle polozena otazka nema smysl, obe verze maji sva opodstatneni a zalezi, o co se snazis: Queue je asynchronni tj. zalezi, jak jsou predana data do hlavniho threadu (pokud se nejaka pouzivaji) a jak je zajistena jejich platnost a konzistence v dobe, az se hlavni thread dostane k tomu, aby s nimi neco delal.
 
Citace
Když ve vláknu pracuji se souborem, například přes TFileStream, je správné ohraničit od TFileStream.Create až po TFileStream.Free kritickou sekcí?
Je tak mozne docilit vylucny pristup k souboru, ale zrovna u souboru se pri sdileni rozlisuji operace, ktere jsou povoleny ostatnim napr. ze cist ze souboru muze kazdy, kdo o to ma zajem aj.

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 309
  • Karma: 0
Dobře, a poslední otázka. Musím využívat kritické sekce, když z hlavního vlákna aplikace předávám hodnoty (skrze nějakou proceduru ve smyslu NovaHodnota(a: string), ktere ulozi hodnotu a do bufferu vlakna) do běžícího vlákna, které s těmito daty pracuje v Execute sekci? Příklad:

Kód: Delphi [Vybrat]
  1. TMujLog = class(TThread)
  2. private
  3.   buffer: array of string;
  4. protected
  5.   procedure Execute; override;
  6. ...
  7. ...
  8. public
  9.   procedure NovaHodnota(const s: string);
  10. end;
  11.  
  12. procedure TMujLog.NovaHodnota(const s: string);
  13. begin
  14.   SetLength(buffer, length(buffer) + 1);
  15.   buffer[ length(buffer) - 1] := s;
  16. end;
  17.  
  18. procedure TMujLog.Execute;
  19. var
  20.   i: integer;
  21. begin
  22.   repeat
  23.     for a := length(buffer) - 1 downto 0 do
  24.     begin
  25.        UlozDoSouboru(buffer[i]);
  26.        SetLength(buffer, length(buffer) - 1);
  27.     end;
  28.   until Terminated;
  29. end;
  30.  

Otázka zní, zda může procedura NovaHodnota "kolidovat" s činností Execute? Musí se to ošetřovat kritickou sekcí, popřípadě jinak? Procedura NovaHodnota je volaná z hlavního vlákna.

Děkuji.

Offline < z >

  • Administrátoři
  • Guru
  • *****
  • Příspěvků: 1166
  • Karma: 44
    • Verze Delphi: 7, 2010
To už je asi jen opakování. Pokud v jednom momentě může nastat zavolání NovaHodnota z hlavního vlákna a zpracování v Execute, tak musíš použít nějaký mechanismus synchronizace.

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 309
  • Karma: 0
To už je asi jen opakování. Pokud v jednom momentě může nastat zavolání NovaHodnota z hlavního vlákna a zpracování v Execute, tak musíš použít nějaký mechanismus synchronizace.

Mám teď v tom bordel. Z hlavního vlákna se nemá používat Synchronize. Hlavní vlákno ale plní data pro vedlejší vlákno skrze NovaHodnota. Kritická sekce se má využívat v případě více vláken a i když mám pouze jednu instanci vedlejšího vlákna, musím tedy použít kritickou sekci?

Pokud ano, s ohledem na můj příklad, musí být kritická sekce jen v proceduře NovaHodnota, a nebo musí být i v Execute v místě, kdy pracuji s polem buffer?

Děkuji
« Poslední změna: 23-09-2021, 06:58:38 od age.new »

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 3280
  • Karma: 139
    • Verze Delphi: D2007, XE3, DX10
Kritická sekce se má využívat v případě více vláken a i když mám pouze jednu instanci vedlejšího vlákna, musím tedy použít kritickou sekci?
Jakmile mas vice nez jeden thread a provadis operace se sdilenymi daty (obecne prostredky), jejichz povaha neni takova, ze nemohou byt preruseny OS a cas procesoru pridelen jinemu threadu, ktery se stejnymi daty operuje, musis tu neprerusitelnost operace s daty zajistit. A jedna z cest, jak toho dosahnout, je pouzit kritickou sekci tj. vlastne jakesi navestidlo, ktere bude signalizovat, ze muzes s daty operovat. Pokud nemuzes, tak OS thread suspenduje a necha ho na dostupnost cekat v rezii OS.

Takze jestli mas buffer, ktery z jedne strany nekdo plni a z druhe z nej odebira, musi byt tyto operace celistve a vzajemne vylouceny => k bufferu musis vytvorit kritickou sekci a kazdy, kdo s bufferem chce operovat, musi do teto kriticke sekce vstoupit.

##

Ale to je archaicky pristup: buffer ve forme neustale realokovaneho pole patri mezi nejhloupejsi varianty implementace FIFO -> Delphi uz docela dlouho obsahuje implementaci FIFO TQueue. Ta neni thread safe, ale je to objekt, takze pracujes s jeho instanci a proto existuji ty monitory, ktere pouzijes misto kriticke sekce:
Kód: Delphi [Vybrat]
  1. TMonitor.Enter(FQueue);
  2. try
  3.   ...
  4. finally
  5.   TMonitor.Exit(FQueue);
  6. end;
  7.  
Je to daleko prehlednejsi a udrzovatelnejsi, nemusis ke kazde datove strukture vytvaret kritickou sekci a kazdy na prni pohled vidi, co a proc ten kod dela.

Takze to das dovnitr metody NovaHodnota, kde si zamknes pristup k vnitrnim datum, nad kterymi potrebujes operovat, na nezbytne nutnou dobu. A stejne v Execute si musis vyhradit, opet na nezbytne nutnou dobou, pristup k vnitrnim datum (FIFO), se kterymi chces neco delat tj. na odebrani polozky z FIFO -> vlastni prace s polozkou uz je lokalni zalezitosti. Protoze kdyz to nebude na nezbytne nutnou dobu, tak ti bude k h*vnu i 100 jadrovy procesor.
« Poslední změna: 23-09-2021, 08:55:40 od pf1957 »

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 3280
  • Karma: 139
    • Verze Delphi: D2007, XE3, DX10
Z hlavního vlákna se nemá používat Synchronize.
Nesmi, vedlo by to k deadlocku. Synchronize prenese vykonani operace z podruzneho do hlavniho threadu a pokud jsi v hlavnim threadu, nedava toto volani smysl.

U Queue, kde se neceka na vykonani dane operace a nehrozi deadlock, se pri zavolani z hlavniho threadu operace vykona hned, jako bezne volani subroutiny. A k tomu existuje jeste ForceQueue, kterou se vynuti vlozeni pozadavku do fronty a pozadavek bude zpracovan az v dalsim cyklu pumpy zprav.

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 309
  • Karma: 0
Děkuji moc za vysvětlení. Už to chápu.

Offline raul

  • Hrdina
  • ****
  • Příspěvků: 416
  • Karma: 15
    • Verze Delphi: FPC :D
Kód: Delphi [Vybrat]
  1. procedure TMujLog.NovaHodnota(const s: string);
  2. begin
  3.   SetLength(buffer, length(buffer) + 1);
  4.   buffer[ length(buffer) - 1] := s;
  5. end;
  6.  
  7. procedure TMujLog.Execute;
  8. var
  9.   i: integer;
  10. begin
  11.   repeat
  12.     for a := length(buffer) - 1 downto 0 do
  13.     begin
  14.        UlozDoSouboru(buffer[i]);
  15.        SetLength(buffer, length(buffer) - 1);
  16.     end;
  17.   until Terminated;
  18. end;
  19.  

Takhle ne. Mas tam nekolik moznych setkani, ktera neprobehnou moc dobre.

  SetLength(buffer, length(buffer) + 1);
       UlozDoSouboru(buffer);
  buffer[ length(buffer) - 1] := s;
       SetLength(buffer, length(buffer) - 1);
Ulozi se ti - nic.

  SetLength(buffer, length(buffer) + 1);
       UlozDoSouboru(buffer);
aaa := length(buffer) - 1;
       SetLength(buffer, length(buffer) - 1);
  buffer[ aaa ] := s;
Zde jsem si pomohl promenou aaa - nicmene, stat se to muze. Zapisujes tedy do pole na pozici, ktera jiz neni platna.

Realne bych tohle resil misto polem nejakym linked listem, kde muzes pouzit metody Interlocked, tzn neni treba nic synchronizovat.
V execute si resis zapis dokud mas odkaz na next zaznam, pridani stringu resis vytvorenim zaznamu a jeho Interlocked vlozenim jako odkaz do posledniho platneho zaznamu.
Lazarus 1.6.3:), FPC, Intel/Arm, Windows/Linux

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 309
  • Karma: 0
Tak já jsem to vyřešil použitím TCriticalSection, viz příklad kam jsem to cca vložil. Jinak v mé verzi nepoužívam array, ale spojitý seznam. V příkladu je array jenom pro jednodušší představu:

Kód: Delphi [Vybrat]
  1. procedure TMujLog.NovaHodnota(const s: string);
  2. begin
  3.   Lock.Acquire;
  4.   SetLength(buffer, length(buffer) + 1);
  5.   buffer[ length(buffer) - 1] := s;
  6.   Lock.Release;
  7. end;
  8.  
  9. procedure TMujLog.Execute;
  10. var
  11.   i: integer;
  12. begin
  13.   repeat
  14.     Lock.Acquire;
  15.     for a := length(buffer) - 1 downto 0 do
  16.     begin
  17.        UlozDoSouboru(buffer[i]);
  18.        SetLength(buffer, length(buffer) - 1);
  19.     end;
  20.     Lock.Release;
  21.   until Terminated;
  22. end;
  23.  

Offline raul

  • Hrdina
  • ****
  • Příspěvků: 416
  • Karma: 15
    • Verze Delphi: FPC :D
Kód: Delphi [Vybrat]
  1. procedure TMujLog.Execute;
  2. var
  3.   i: integer;
  4. begin
  5.   repeat
  6.     Lock.Acquire;
  7.     for a := length(buffer) - 1 downto 0 do
  8.     begin
  9.        UlozDoSouboru(buffer[i]);
  10.        SetLength(buffer, length(buffer) - 1);
  11.     end;
  12.     Lock.Release;
  13.   until Terminated;
  14. end;
  15.  

Safr, kdy ze je ten spravnej cas na pridani polozky? (Mezi until terminated a repeat ?) Budes si zbytecne brzdit zapisovaci vlakna (mozna nevadi, ale..). Proto jsem zminoval linkedlist, neb tam ti zalezi jen na posledni polozce a zbytek muze klido ject plne paralerne, zde se bude neustale zamykat, odemykat a asi i dost cekat (zalezi na aplikaci, my do logu piseme hoodne, takze pro nas by to bylo kriticke).

A jen info - pises to do souboru obracene schvalne? Pokud kod chces mit takto, asi bych setlength mrsknul za cyklus rovnou na 0.
Lazarus 1.6.3:), FPC, Intel/Arm, Windows/Linux