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

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 309
  • Karma: 0
Vážená skupino,

mohl bych Vás poprosit o jednoduchý příklad, jak správně zavolat náročnou proceduru ve vláknu a v případě potřeby ji okamžitě zrušit? Zní to jednoduše, ale tápu, zda a jak vůbec použít WaitForSingleObject. Jde mi o "správné" zrušení vlákna, kdy padne do terminated stavu aniž bych musel čekat na doběh eventu.

Příklad pro jednoduché pochopení:

procedure UlozDataNaHDD; <- tato procedure začne ukládat velké množství dat (řádově i stovky MB) do souboru na HDD

Zavolání UlozDataNaHDD je časově náročné a může trvat i několik vteřin. Zavolání ale může přijít i několikrát za sekundu (záleží na uživateli).

Na každé zavolání bych potřeboval vytvořit vlákno, které si bokem spustí proceduru UlozDataNaHDD. V případě, že přijde požadavek v momentě, že vlákno stále běží, je nutné vlákno okamžitě přerušit. Je jedno co se uložilo a neuložilo, protože ty data jsou již neplatná. V momentě, kdy dojde k přerušení vlákna se obratem vytvoří nové vlákno s UlozDataNaHDD a celý proces se opakuje od začátku.

PS: důležité je to "okamžité přerušení vláka" a jeho opětovné vytvoření, aby se data začali ukládat odznovu. Tj. ty původní soubory, se hned před ukládáním vymažou, protože jsou již neplatná.

Děkuji za pomoc.

 
« Poslední změna: 15-09-2021, 14:28:50 od age.new »

Offline Jan Fiala

  • Plnoletý
  • ***
  • Příspěvků: 239
  • Karma: 3
    • Verze Delphi: 10.4.1
    • PSPad editor
A neměl bys počkat ve druhém vláknu, až to první doběhne?
Ty budeš dělat tohle:
spustíš vlákno
spustíš druhé, to zjistí, že první běží a ukončí se a před ukončením by mělo spustit další vlákno a stále dokola

Asi bych zavedl nějaký semafor - vlákno jej při spuštění nastaví a při ukončení shodí.
Druhé vlákno se při spuštění podívá, zda je semafor nastaven a pokud ano, bude čekat (můžeš zavést i timeout, aby se po nějaké době ukončilo, pokud by to šlo vše do kytek) Až první vlákno skončí a nastaví semafor, tak se čekající vlákno rozběhne.

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 309
  • Karma: 0
Semafor je přesně to, co nechci. Ukládání, tedy běh vlákna, může být například i 20 vteřin. Když ale přijde požadavek na nové uložení již po 5 vteřinách, musel bych čekat na dokončení původního vlákna. Nehledě na to, že při čekání na dokončení původního vlákna by mohl přijít další a další požadavek na nové uložení. Proto je nutné původní vlákno ihned přerušit + uvolnit soubory pro nové ukládání.

Offline vandrovnik

  • Guru
  • *****
  • Příspěvků: 1274
  • Karma: 51
    • Verze Delphi: 10.3
A nestačí, aby to vlákno (potomek tThread), které provádí ukládání, během práce testovalo příznak Terminated? Ten se z hlavního vlákna nastavuje zavoláním .Terminate...

Offline Jan Fiala

  • Plnoletý
  • ***
  • Příspěvků: 239
  • Karma: 3
    • Verze Delphi: 10.4.1
    • PSPad editor
Proto je nutné původní vlákno ihned přerušit + uvolnit soubory pro nové ukládání.

Aha, To jsem z toho původního nepochopil, že chceš ukončit původní vlákno.
Jak psal vandrovnik, musíš kontrolovat v původním vláknu nastavený příznak terminated, ale to při kopírování velkého souboru může také nějakou dobu trvat. Pokud máš vlastní mechanismus na kopírování, pak by to problém být neměl, můžeš testovat i v průběhu kopírování a to v případě potřeby přerušit.

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 3292
  • Karma: 139
    • Verze Delphi: D2007, XE3, DX10
PS: důležité je to "okamžité přerušení vláka" a jeho opětovné vytvoření, aby se data začali ukládat odznovu. Tj. ty původní soubory, se hned před ukládáním vymažou, protože jsou již neplatná.
"okamzite" to nedocilils ani sestrelenim procesu, tj. volanim TerminateProcess, protoze ceka na pending I/O...
Co se da bezne udelat, rozdelit ten zapis napr. po bufferech a mezi jednotlivymi zapisy bufferu otestovat Terminated.

##

Nevim, co ma byt  cilem snazeni, jestli "plati posledni data", pak zalezi, jake je riziko jejich overrunu ve vztahu k trvani zapisu bufferu do souboru. Pokud neni, tak staci rict prave zapisujicimu threadu, za ma udelat rewind a psat nova data. Pokud overrun hrozi, tak jedine FIFO se semaforem, u ktereho ceka nekolik threadu, kazdy zapisuje do nejakeho vlastniho docasneho souboru, pri vyskytu novych dat nejaky broadcast do ostatnich threadu, ze to maji zabalit a po dokonceni "commit" v podobe prejmenovani souboru. Ev. jeden thread cekajici u semaforu a vytvarejici worker thready pro zapis, ktery si je osefuje a ten commit u posledniho zaridi.

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 309
  • Karma: 0
Děkuji za příspěvky. Jsem trochu zklamaný, že nejde vlákno jednoduše terminovat a tím docílit jeho ukončení nezávisle na prováděném kódu v Execute. Testování, během práce vlákna, na stav terminated občas používám, ale není to "okamžité". Když pak čekám u WaitFor, může se to protáhnout i na sekundy, podle toho v jaké části kódu vlákno je. Navíc pak musí být v kódu pro ukládání mnoho testovacích podmínek.

Rewind běžícího vlákna je vpodstatě to samé. Více vláken s ukládáním do "svého" temporary souboru jsem zamítl, aby v jeden čas neběželo více paralelních ukládání - a tím se vše ještě více zpomalovalo.

 
« Poslední změna: 16-09-2021, 07:16:50 od age.new »

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 3292
  • Karma: 139
    • Verze Delphi: D2007, XE3, DX10
Děkuji za příspěvky. Jsem trochu zklamaný, že nejde vlákno jednoduše terminovat a tím docílit jeho ukončení nezávisle na prováděném kódu v Execute. Testování, během práce vlákna, na stav terminated občas používám, ale není to "okamžité". Když pak čekám u WaitFor, může se to protáhnout i na sekundy, podle toho v jaké části kódu vlákno je. Navíc pak musí být v kódu pro ukládání mnoho testovacích podmínek.
Proste v praxi nemuzes pouzivat reseni, ktera se hodi tak do skolnich uloh... Bez kooperace na strane threadu se to neobejde a zcela jiste se to pri dnesnich moznostech HW da naprogramovat tak, aby odezva threadu i jeho vykonnost byla uspokojiva, pokud se nejedna o neco extremne narocneho na cas.

Offline < z >

  • Administrátoři
  • Guru
  • *****
  • Příspěvků: 1171
  • Karma: 44
    • Verze Delphi: 7, 2010
Vlákno jde sestřelit pomocí TerminateThread, ale jestli pf říká, že I/O stejně musí nějak doběhnout, tak to stejně nepomůže. Navíc už samotné použití je dost za hranou.
Lepší je fakt nechat 1 sekundu vlákno dojet a mezitím už jet nová data v dalším vláknu. To nemůže být výkonový problém, protože tak jako tak I/O musí dojet.

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 309
  • Karma: 0
Přepracoval jsem ukládání souborů a pomocí TFileStream jsem se dostal na výborné časy - cca 250MB dat za 1 sekundu. Ale i přesto bych rád celé ukládání spustil v threadu. Mám ale problém, jak si to vše uspořádat. V každé proceduře mám vlastní logování událostí (nazývám to Trace), které je samo o sobě vlákno starající se o správu logu (třídění, ukládání do soubor atd.). Zároveň mám důležité části kódů v try - except blocích, které v případě chyby předá zprávu do logování chyb, které je opět řízené ve vláknu. Obě vlákna se navzájem neovlivňují a pracují vedle sebe.

Důležitá poznámka - všechny procedury a funkce mám v různých unitech (globální), roztříděných podle toho co provádějí a s jakýmy daty pracují. Když si vytvořím vlákno (potomka TThread), tak si nejsem jistý, zda je "thread safe" volat procedury a funkce mimo třídu vlákna. Přepisovat do třídy vlákna, ležícího v jedné unitě, procedury a funkce ležící jinde by mi v tom udělalo chaos. Níže na příkladu uvedu, co mám na mysli:

// unit_1.pas
TMyThread = class(TThread)

// unit_2.pas
Data = array[0..9] of TData;
procedure Uloz_vsechna_data;

// unit_3.pas
procedure Vymaz_soubory(TData);
procedure Uloz_do_souboru(TData);

Průběh:
Procedure Uloz_vsechna_data:
- podmínka, zda běží vlákno TMyThread, pokud ano, přes Event jej nechám začít od začátku, pokud ne, spustím jej.
- ve vláknu TMyThread se spustí cyklus "for a := 0 to length(Data) - 1" a v každém cyklu:
    1) Vymaz_soubory(Data[a])
    2) Uloz_do_souboru(Data[a])

Obě procedury Vymaz_soubory a Uloz_do_souboru obsahují množství Trace a try-except bloků a obě procedury mohu volat kdykoliv jindy i mimo proceduru Uloz_vsechna_data, tj. například na stisk tlačítko zavolám jen proceduru Vymaz_soubory(Data[a]). Samozřejmě si ohlídám, abych vláknu nezměnil data (nevymazal soubory) při jeho činnosti.

Je to tak v pořádku?

Děkuji.



« Poslední změna: 20-09-2021, 09:00:36 od age.new »

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 3292
  • Karma: 139
    • Verze Delphi: D2007, XE3, DX10
Vlákno jde sestřelit pomocí TerminateThread, ale jestli pf říká, že I/O stejně musí nějak doběhnout, tak to stejně nepomůže.
To jsem popletl a zamenil si TerminateProcess s TerminateThread :-( U toho threadu to nebude s temi I/O tak zhave, i kdyz vlastni I/O stejne neprobihaji na urovni user modu, na disky se psavalo pres DMA, dneska to snad zajistuje nejaky PCI radic...  Pri sestreleni threadu zrejme hned zakazou dalsi prideleni procesoru v user modu.

V kazdem pripade je prasarna zaradit neco takoveho do normalni logiky aplikace, protoze to muze nechat radu sdilenych prostredku v nedefinovanem stavu.

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 3292
  • Karma: 139
    • Verze Delphi: D2007, XE3, DX10
Mám ale problém, jak si to vše uspořádat. V každé proceduře mám vlastní logování událostí (nazývám to Trace), které je samo o sobě vlákno starající se o správu logu (třídění, ukládání do soubor atd.). Zároveň mám důležité části kódů v try - except blocích, které v případě chyby předá zprávu do logování chyb, které je opět řízené ve vláknu. Obě vlákna se navzájem neovlivňují a pracují vedle sebe.
Z toho popisu jsem Gogo a za logovanim, ktere je samo o sobe threadem, si nedokazu predstavit nic, co by zasahovalo do logiky aplikace: kdyz potrebuju logovat z nejakeho threadu, tak jednoduse zavolam nejakou vhodnou funkci zpravidla globalne pristupneho loggeru a o nic dalsiho se nestaram - zbytek zarizuje logger: ten se musi vyporadat s tim, ze ve stejny cas muze byt volan z ruznych threadu, ze si pripadne data nacpe do vnitrni fronty a rizeni vrati volajicimu, aby nezdrzoval s I/O operaci a pak nejakym internim threadem flushne tu frontu do souboru...

Citace
Když si vytvořím vlákno (potomka TThread), tak si nejsem jistý, zda je "thread safe" volat procedury a funkce mimo třídu vlákna.
Aby bylo neco thread safe, tak to nesmi volne (jako v single-threaded rezimu) pracovat s globalnimi prostredky tj. promennymi, pameti, soubory, I/O etc..., To nijak nesouvisi s tim, jestli je to metoda nebo standalone procedura.

Citace
Je to tak v pořádku?
Dal uz jsem to necetl, protoze me to vycerpalo. Prestoze jsem programoval davno pred vznikem objektoveho paradigmatu, tak me udivuje, ze stale se najdou lide, kteri jak rikaval Ruda Pecinovsky: "dal tvori svoje aplikace proceduralnim zpusobem, jen v objektovych jazykach" :-(
« Poslední změna: 20-09-2021, 09:28:13 od pf1957 »

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 309
  • Karma: 0
To logování mám udělané tak, abych dokázal zpracovat i tisíce volání bez narušení činnosti běhu aplikace, tj. hlavního vláka. Doslova "pár řádků" tak, aby to bylo co nejméně problematické a "blbu vzdorné". A funguje to velmi dobře.

Mimochodem, programuji tak, jak mě učili na škole, tak jak programují i zkušenější programátoři a jak programují i lidé na různých forech. Je to OOP, ale možná trochu jinak, než jej používáte Vy. Mám kolegy programátory a velmi často bych některé části kódu psal jinak. To ale neznamená, že je jejich kód nefunkční, nebo špatný. 
« Poslední změna: 20-09-2021, 10:10:48 od age.new »

Offline < z >

  • Administrátoři
  • Guru
  • *****
  • Příspěvků: 1171
  • Karma: 44
    • Verze Delphi: 7, 2010
Vlákna nejsou nic zas tak převratného a složitého. Po troše googlení najdeš všechno.
Ono to je už ve standardu v Delphi, kdy ti samy založí novou jednotku s vláknem. Ty jen dáš do execute svůj "výpočet".
Jediné navic musíš udělat nějaký vlastní create (předat parametry) a synchronize (vrátit výsledky). Pro zastavení jen voláš terminate.

Takhle můžeš mít i víc vláken, to si jen schováš do nějakého TList apod.

Já mám logger ve vlastním vlákně. Jen mu thread-safe předám data (critical section) a on se stará ve vlastním vlákně o uložení ve vhodnou dobu.
Celé to je asi o 20 víc řádků delší, než máš ty.

A zde ještě můj oblíbený vzor
http://semi.gurroa.cz/Clanky/Threads.html#ThreadProc

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 309
  • Karma: 0
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?

Offline vandrovnik

  • Guru
  • *****
  • Příspěvků: 1274
  • 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ů: 3292
  • 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ů: 1274
  • 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ů: 3292
  • 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ů: 1171
  • 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ů: 3292
  • 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ů: 3292
  • 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ů: 423
  • 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, (občas Delphi)

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ů: 423
  • 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, (občas Delphi)

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 309
  • Karma: 0
Se vším souhlasím. Je to jen příklad. Původně jsem si nebyl jistý, zda mohu TCriticalSection použít ve dvou různých procedurách. Když to jde, dělám is lokální kopie a kritickou sekci srážím na co nejmenší blok kódu. V tomto případě ani není rozumné ukládat řádek po řádku, ale prsknout vše naráz atd.

Offline vandrovnik

  • Guru
  • *****
  • Příspěvků: 1274
  • Karma: 51
    • Verze Delphi: 10.3
Ještě bych ten vlastní kód obalil try-finally:

Kód: Delphi [Vybrat]
  1. Lock.Acquire;
  2. try
  3.  ...
  4. finally
  5.  Lock.Release;
  6. end;

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 3292
  • Karma: 139
    • Verze Delphi: D2007, XE3, DX10
Safr, kdy ze je ten spravnej cas na pridani polozky? (Mezi until terminated a repeat ?) Budes si zbytecne brzdit zapisovaci vlakna (mozna nevadi, ale..).
No to je presne ukazka, jak ta implementace nema vypadat. Za me skoda casu mu neco radit.

Offline Stanislav Hruška

  • Padawan
  • ******
  • Příspěvků: 6058
  • Karma: 44
    • Verze Delphi: W10 + D11
Nie je to priamo k problematike, ale možno Ťa to osloví
https://blogs.embarcadero.com/new-ide-plugin-parnassus-parallel-debugger/
W10 64b, Delphi 10.4, FireBird 3.05
Expert na kladenie nejasne formulovaných otázok.