| Autor |
Beitrag |
agapanthus
Hält's aus hier
Beiträge: 11
|
Verfasst: Mi 08.03.06 09:32
Moin.
Ich habe ein Problem mit einem Programm, welches regelmäßig Threads startet. Es wird zum Speicherfresser und ich verstehe es noch nicht ganz, warum...
Das kleine Programm unten ist wenig sinnvoll, verdeutlicht aber das Problem. Es startet in einer Schleife 500 Threads (function) und wartet dann 5 Sekunden, in diser Zeit haben sich alle Threads wieder beendet. Die Threads machen nichts weiter als eine Ausgabe. Der Anstieg des Speicherverbrauchs hängt z.B. von der Länge des ausgegebenen Strings ab, je länger der String, um so schneller der Anstieg.
Kommentiere ich nun ExitThread(0) aus, dann bleibt der Speicherverbrauch konstant. Warum? Laut WinAPI wird der ThreadStack beim Aufruf von ExitThread wieder freigegeben:
"ExitThread is the preferred method of exiting a thread. When this function is called (either explicitly or by returning from a thread procedure), the current thread's stack is deallocated and the thread terminates. The entry-point function of all attached dynamic-link libraries (DLLs) is invoked with a value indicating that the thread is detaching from the DLL."
Darf ich ExitThread nicht in einer Funktion aufrufen? Zugegeben: ExitThread ist einer Funktion irgendwie doppelt gemoppelt: den Rückgabewert setzte ich mit result, ExitThread tut dies auch noch. Starte ich den Thread als Prozedur, ist der Speicherverbrauch konstant, hier macht ExitThread auch Sinn, so kann ich auch einen Wert zurückgeben, den ich mit GetExitCodeThread abfragen könnte.
Also: Warum frisst ein Thread, der als Funktion gestartet wurde und der mit der "preferred method" ExitThread beendet wurde, Speicher?
Ein etwas verwirrter Gruß an die Spezialisten von
Frank
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41:
| program Thread; {$APPTYPE CONSOLE} uses windows, sysutils;
procedure myThread(p: Pointer); stdcall; begin write(' ' + intToStr(Random(1000))); ExitThread(0); end;
function myThread(p: Pointer): DWORD; stdcall; begin write(' ' + intToStr(Random(1000))); result := 0; ExitThread(0); end;
var tId:DWORD; i:integer = 0; begin while (TRUE) do begin CreateThread(nil, 0, @myThreadFunction, nil, 0, tId); inc(i); if i > 500 then begin write(#10#13 + '**********Pause*************' + #10#13); sleep(5000); i:=0; end; end; end. |
|
|
Udontknow
      
Beiträge: 2596
Win7
D2006 WIN32, .NET (C#)
|
Verfasst: Mi 08.03.06 09:38
Hallo!
Eine Frage vorweg: Warum nutzt du nicht die Kapselung TThread? Willst du Non-VCL programmieren?
Grundsätzlich ist die Frage, ob der Aufruf von "write" in einer Konsolenanwendung überhaupt threadsicher ist. Solange da nichts zu steht, würde ich grundsätzlich von Gegenteiligem ausgehen und solche "Ausgaben" synchronisieren, was aber in der Konsolenanwendung im Hauptthread dann noch berücksichtigt werden muss (Siehe Synchronize und CheckSynchronize).
Cu,
Udontknow
|
|
Grendel
      
Beiträge: 443
Gentoo Linux, MacOS X, Win 2000
D5 Ent, D7 Ent, Lazarus, Anjuta, MonoDevelop
|
Verfasst: Mi 08.03.06 10:00
| Zitat: | | The thread object remains in the system until the thread has terminated and all handles to it have been closed through a call to CloseHandle. |
Also CloseHandle() für jedes Thread-Handle aufrufen.
Dafür musst Du natürlich das hier umbauen:
Delphi-Quelltext 1: 2:
| while (TRUE) do begin CreateThread(nil, 0, @myThreadFunction, nil, 0, tId); |
da Du dir hier das Handle (tId) immer wieder überbügelst.
Bis neulich ...
|
|
Motzi
      
Beiträge: 2931
XP Prof, Vista Business
D6, D2k5-D2k7 je Prof
|
Verfasst: Mi 08.03.06 10:24
Man sollte nie CreateThread/ExitThread direkt aufrufen, sondern die entsprechenden Funktionen des verwendeten Frameworks die diese Funktionen kapseln. Im Fall von Delphi ist das BeginThread/EndThread. Weshalb das so ist kannst du in der Hilfe nachlesen.
Abgesehn davon bin ich mir ziemlich sicher (falls ich hier Blödsinn verzapfe bitte bescheid sagen), dass Writeln nicht threadsafe ist und daher nur synchronisiert aufgerufen werden sollte.
Gruß, Motzi
_________________ gringo pussy cats - eef i see you i will pull your tail out by eets roots!
|
|
agapanthus 
Hält's aus hier
Beiträge: 11
|
Verfasst: Mi 08.03.06 11:25
Hallo Udontknow, Grendel und Motzi,
danke für Eure Antworten.
Udontknow hat folgendes geschrieben: |
Eine Frage vorweg: Warum nutzt du nicht die Kapselung TThread? Willst du Non-VCL programmieren?
|
Ja, das ganze ist "non-VCL".
Grendel hat folgendes geschrieben: | | Zitat: | | The thread object remains in the system until the thread has terminated and all handles to it have been closed through a call to CloseHandle. |
Also CloseHandle() für jedes Thread-Handle aufrufen. |
Ok, baue ich ein.
Grendel hat folgendes geschrieben: |
Dafür musst Du natürlich das hier umbauen:
Delphi-Quelltext 1: 2:
| while (TRUE) do begin CreateThread(nil, 0, @myThreadFunction, nil, 0, tId); |
da Du dir hier das Handle (tId) immer wieder überbügelst.
|
tId ist doch "nur" eine ID, das Handle liefert Create- /BeginThread als Rückgabewert. Und den muß ich CloseHandle dann übergeben. Wenn tId ständig überschrieben wird, sollte das doch keinen stören. Oder doch?
Motzi hat folgendes geschrieben: | | Man sollte nie CreateThread/ExitThread direkt aufrufen, sondern die entsprechenden Funktionen des verwendeten Frameworks die diese Funktionen kapseln. |
Ok, verstanden und geändert.
Motzi hat folgendes geschrieben: | | Abgesehn davon bin ich mir ziemlich sicher (falls ich hier Blödsinn verzapfe bitte bescheid sagen), dass Writeln nicht threadsafe ist und daher nur synchronisiert aufgerufen werden sollte. |
WriteLn war auch nur ein Beispiel, damit man "was sieht", wenn das Testprogramm läuft. In wirklichen Leben macht der Thread was anderes und schreibt sein Ergebnis ordentlich in eine Struktur, deren Adresse er übergeben bekommen hat und die exklusiv für diesen Thread da ist.
Offen ist noch die Frage, ob ich in einer Funktion, die als Thread gestartet wurde, endThread ausführen muß - in den Beispielen zu Luckies Thread-Tutorial wird endThread auch nie benutzt...?
Ich werde mein Programm entsprechend ändren und dann testen...
Danke, Gruß Frank
|
|
Luckie
Ehemaliges Mitglied
Erhaltene Danke: 1
|
Verfasst: Mi 08.03.06 12:11
Wenn du das Handle nicht mehr brauchst:
Delphi-Quelltext 1:
| CloseHandle(BeginThread(...)); |
|
|
agapanthus 
Hält's aus hier
Beiträge: 11
|
Verfasst: Mi 08.03.06 12:45
Titel: Noch brauch ich es...
Mahlzeit.
Luckie hat folgendes geschrieben: | Wenn du das Handle nicht mehr brauchst:
Delphi-Quelltext 1:
| CloseHandle(BeginThread(...)); | |
Doch, im Moment brauche ichs noch. Der Thread wird per Timer aus dem HauptThread überwacht, wenn er nicht in der gewünschten Zeit zu Ende ist, wird er mittels TerminateThread abgeschossen - mit dem unangenehmen Nebeneffekt, daß der ThreadStack nicht richtig frei gegeben wird. Alle anderen Resourcen, die der abgeschossene Thread benutzt hat, räumt der HauptThread wieder auf.
Im Moment kann ich auf diese Zeitüberwachung nicht verzichten, muß mal sehen, ob ich das anders lösen kann.
Gruß Frank
|
|
Luckie
Ehemaliges Mitglied
Erhaltene Danke: 1
|
Verfasst: Mi 08.03.06 13:46
Dann lass den Timer einen Flag setzen, der im Thread überprüft wird und wenn das Flag gesetzt ist, dann beendet sich der Thread eben selber. TerminateThread ist ganz gefährlich. Der Thread braucht nur irgendwelche Objekte mit CruiticalSectuion locken, wenn du ihn dann abschießt, werden diese Objekte nicht mehr freigegeben und andere Threads, die eventuell darauf warten, für alle Ewigkeit blockiert.
|
|
agapanthus 
Hält's aus hier
Beiträge: 11
|
Verfasst: Do 09.03.06 13:54
Titel: Erledigt...
Hallo Luckie
Luckie hat folgendes geschrieben: | | Dann lass den Timer einen Flag setzen, der im Thread überprüft wird und wenn das Flag gesetzt ist |
Gute Idee, nun wird auch der (eigentlich nicht auftretende) Fall, daß ein Thread nicht fertig wird, ordentlich behandelt. Und das Programm läuft ohne Speicherleck.
Danke für die Hilfe hier, Gruß Frank
|
|
|