16. oktober 2007 - 00:34Der er
14 kommentarer og 3 løsninger
leak med dynamic array
Hej
Mit spørgsmål er i to dele:
1) Hvis jeg har et dynamic array som indeholder fx 1000 string værdier. Når jeg afslutter mit program så kan jeg sætte arrayet til nil, og så ved jeg at der ikke er noget leak. fx
var A: Array of string; I: Integer; begin SetLength(A, 1000); for I:=0 to 999 do A[I]:='et eller andet'; A:=nil; end;
Men hvad sker der hvis jeg IKKE sætter arrayet til nil til allersidst ? Vil jeg da ikke få en leak ? fx
var A: Array of string; I: Integer; begin SetLength(A, 1000); for I:=0 to 999 do A[I]:='et eller andet'; end;
Det gør jeg nemlig ikke ifølge EurekaLog, men det undrer mig meget, for hvad bliver der af al hukommelsen som blev allokeret? Delphi free'er jo ikke automatisk dynamic arrays så vidt jeg ved.
2) Så har jeg et andet spørgsmål Jeg har en timer. I denne timers OnTimer procedure har jeg et dynamic array som indeholder tråd-objekter. Disse tråde kører indtil de er færdige, og så bliver de free'et ved afslutning (FreeOnTerminate=True).
procedure OnTimer var A: Array of TThread; I, X: Integer; begin SetLength(A, X) for I:=0 to X-1 do A[I]:= TThraed.Create end;
Når timer eventen næste gang bliver aktiveret, kommer jeg jo igen ind til den samme procedure, hvor jeg har mit dynamiske array. På dette tidspunkt ved jeg ikke om trådene fra "sidste gang" jeg var her, er færdige eller ej. Men kan jeg nu tillade at initiere arrayet og create trådene igen? Det jeg er bange for, er hvis jeg havner i samme hukommelses-adresse som sidst, og dermed kommer til at overskrive trådende fra sidste kørsel, som muligvis stadig er igang med at eksekvere.
Et andet problem er: vil arrayet blive free'et når alle tråde er færdige med at køre, eller får jeg en leak ? Når alle tråde er færdige med at køre, så vil arrayet indeholde "nil" på alle pladser. Men burde jeg så ikke sætte arrayet selv til nil? :
A:=nil.
Og hvis ikke: hvordan og hvornår bliver hukommelsen til selve arrayet (ikke de tråd-objekter det indeholder) free'et ? Dette kan jo ikke forekomme i slutningen at OnTimer proceduren, da denne procedure er afsluttet før trådene er færdige med at køre...
Hvis du har 1000 strengværdier, har du for mange i et dynamisk array. Jeg har eksperimenteret en del med den og det er noget lort. Hvorfor bruger du ikke en TStringList? Den er hurtig, dynamisk og skræddersyet til strenge (som navnet antyder).
Du nulstiller array'en ved SetLength(A,0)
Du kan lade være med at FreeOnTerminate og så løbe listen igennem efter tråde der er Terminated. De kan du pille ud af listen og frigive.
Alternativt har en tråd en OnTerminate-event du kan koble dig på, så når den vælger at dø kan du vha denne fiske det frigivne objekt ud af listen.
Tread.OnTerminate := OnTerminateEvent;
...
procedure TForm1.OnTerminateEvent(Sender: TObject); var index: integer; begin index := fThreadList.IndexOfObject(Sender); if Index >= 0 then fThreadList.Delete(index); end;
Det som det hele egentligt går ud på, er at jeg har X antal klienter som har forbindelse til en server. Serveren skal pinge klineterne med jævne mellemrum. Ping implementeringen er sat ind i en tråd (det skal den, fordi jeg blocking forbindelser).
Jeg vil så lave et array over tråde, som hver især får til ansvar at pinge én klinet. Og disse tråde-objekter (fra arrayen) vil blive created og kørt ind fra en timer, som tidligtere vist:
procedure OnTimer var A: Array of TThread; I, X: Integer; begin SetLength(A, X) for I:=0 to X-1 do A[I]:= TThraed.Create end;
Hvis jeg venter på at alle tråde en "terminated", så halter programmet jo imens (hvis nogle af trådene er langsomme).
Hvis jeg assigner en OnTerminateEvent, så er jeg sådanset ligevidt, fordi hvornår og hvordan vil arraey'et/Threadlist-objektet blive free'et ?
Jeg kunne jo selvfølgelig lave en global TThreadList, men det betyder jo at jeg hvergang skal tjekke for hvilke klinter-pingere der stadig kører, og starte tråde kun for de klinter, hvis ping blev færdig under forrige kørsel. Jeg vil nødigt bruge denne løsning, da det ikke vil være den mest "elegante" løsning :-) Men jeg kan jo selvfølgelig blive nødt til det hvis der opstår en leak, og der ikke er andre muligheder...
Men hele spørgsmålet går ud på om der overhovedet ER en leak. Hvis jeg har FreeOnTerminate til true, så vil jeg ende med et array som indeholder X pladser. Hvert plads er af Typen TThread, men er ligemed nil (når trådene er afsluttede). Så ender jeg med et array af længden X med nil på alle pladser. Skal sådan en free'es eksplicit, eller sørger Delphi for det ? Og hvis Delphi sørger for det, hvornår sker det og hvordan ?
Og er der nogen af jer som ved hvor sådan noget info står ? Jeg kan ihvertfald ikke finde noget i Delphis manual...
Jeg kan ikke se hvordan du kan undgå at vente på dine tråde afslutter - medmindre du kan sætte FreeOnTerminate = true når du lukker hovedprogrammet. Det må du afprøve.
I din mainforms destructor eller OnClose bør du nulstille den vha SetLength. Som Arne skriver frigives den under alle omstændigheder når programmet afsluttes, men Delphi har nu engang kun meget begrænset "Garbage Collector" funktionalitet (ved interfaces) og så frigiver man altså hvad man har allokeret; det er god praksis.
Array'et er lokal (dvs ikke global) og jeg kan ikke sættes length til noget fra OnClose i hovedprogrammet.
Når jeg har startet mine tråde, så behøver jeg jo ikke at vente på dem til de afslutter. Det er jo netop derfor jeg kører tråde, og ikke bare kalder en ping proceudre. I princippet så kan jeg nå at komme ind i OnTimer eventen igen og starte nye tråde, før de gamle tråde er færdige med at køre..
Mht til Delphi's garbage collection, så kan jeg ikke være afhængig af den. Mit server program kommer til at køre langt tid ad gangen, og derfor er det vigtigt at undgå leaks
Hvis du har nogle tråde som er fre on terminate, kan du create dem og execute dem, du kan derefter create de samme VAR's og execute dem, og det kan du gøre alle de gange du vil, det vil ikke gi' problemer :)
Du vil ikke komme til at overskrive noget, din tråd-create allokere en ny "klump" hukommelse til dit obj., det problem man kan få ved at gøre sådan er at din reference til de tidligere tråd-obj. den er væk, fordi du lige har lagt en ny ref i din var :)
Som jeg skriver er der kun garbage collector på interfaces og com-objekter. Det bruger du ikke.
Allokering og deallokering kan klares sådan her (men hvorfor bruge dynamiske arrays? Jeg har spurgt om det flere gange før og gør det igen. Der findes TList, TObjectList og sågar TStringList der er bedre):
procedure EtEllerAndet(Sender: TObject); var A: array of TThread; begin SetLength(A,1000); try finally SetLength(A,0); end; end;
Jeg vil påstå at dynamiske arrays, pga. den sære kode der ligger bag, er mere risikable fremfor nævnte TList varianter. Det er ikke det rigtige at bruge hvis man har et alternativ!
------------ o ------------
I øvrigt: Hvis din DynArray er lokal hvad skal du så bruge den til? Så er det lettere at bruge Martins FireAndForget tip, men du bør sikre dig at tråden faktisk afslutter for ellers er der en lækage der da, mener jeg, tråden ikke afsluttes sammen med mainformen, men lever sit eget liv.
------------ o ------------
Hvad vil der ske om du fyrede trådene af oveni hinanden? Ville den så ikke bare pinge samme maskine flere gange i træk? Hvad ville der ske ved det?
Det er faktisk meget fornuftigt:-) Jeg skal jo ikke bruge referencen til noget. Tråden creates, sendes afsted og jeg glemmer den derefter.
Hvis jeg har forstået det rigtigt, så har jeg slet ikke brug for et array i det hele taget. Jeg skal bare med den samme variable create alle mine tråde. Når jeg er færdig skal jeg bare sætte variablen til nil:
var T: Thread begin for I:=0 to X do begin T:=TThread.Create(True); //suspenderet T.KlientID:= X; //(her sætter jeg det specifikke formål med tråden) T.FreeOnTerminate:=True; T.Resume; end; T:= nil; end;
Jeg vidste ikke man kunne bruge den FireAndForget metode, derfor bøvlede jeg med arrays. Er heller ikke selv vild med dem.
Det er en god pointe med flere tråde der pinger samtidigt. Det har jeg selv overvejet, og sådan som tingene er opbygget, så gør det ikke så meget ( andet end en smule overhead)
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.