Autor Beitrag
agapanthus
Hält's aus hier
Beiträge: 11



BeitragVerfasst: 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


ausblenden volle Höhe Delphi-Quelltext
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;

//*******************************************************************
//   myThreadProcedure
//*******************************************************************
procedure myThread(p: Pointer); stdcall;
begin
  write(' ' + intToStr(Random(1000)));
  ExitThread(0);
end;

//*******************************************************************
//   myThreadFunction
//*******************************************************************
function myThread(p: Pointer): DWORD; stdcall;
begin
  write(' ' + intToStr(Random(1000)));
  result := 0;
  ExitThread(0);
end;

//*******************************************************************
//   main
//*******************************************************************
var
  tId:DWORD;
  i:integer = 0;
begin
  while (TRUE) do begin
    CreateThread(nil, 0, @myThreadFunction, nil, 0, tId);
//    CreateThread(nil, 0, @myThreadProcedure, nil, 0, tId);
    inc(i);
    if i > 500 then begin
     write(#10#13 + '**********Pause*************' + #10#13);
     sleep(5000);
     i:=0;
    end;
  end;
end.
Udontknow
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2596

Win7
D2006 WIN32, .NET (C#)
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 443

Gentoo Linux, MacOS X, Win 2000
D5 Ent, D7 Ent, Lazarus, Anjuta, MonoDevelop
BeitragVerfasst: 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:
ausblenden 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2931

XP Prof, Vista Business
D6, D2k5-D2k7 je Prof
BeitragVerfasst: 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 Threadstarter
Hält's aus hier
Beiträge: 11



BeitragVerfasst: Mi 08.03.06 11:25 
Hallo Udontknow, Grendel und Motzi,

danke für Eure Antworten.

user profile iconUdontknow hat folgendes geschrieben:

Eine Frage vorweg: Warum nutzt du nicht die Kapselung TThread? Willst du Non-VCL programmieren?

Ja, das ganze ist "non-VCL".

user profile iconGrendel 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.

user profile iconGrendel hat folgendes geschrieben:

Dafür musst Du natürlich das hier umbauen:
ausblenden 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?

user profile iconMotzi 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.

user profile iconMotzi 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



BeitragVerfasst: Mi 08.03.06 12:11 
Wenn du das Handle nicht mehr brauchst:
ausblenden Delphi-Quelltext
1:
CloseHandle(BeginThread(...));					
agapanthus Threadstarter
Hält's aus hier
Beiträge: 11



BeitragVerfasst: Mi 08.03.06 12:45 
Titel: Noch brauch ich es...
Mahlzeit.

user profile iconLuckie hat folgendes geschrieben:
Wenn du das Handle nicht mehr brauchst:
ausblenden 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



BeitragVerfasst: 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 Threadstarter
Hält's aus hier
Beiträge: 11



BeitragVerfasst: Do 09.03.06 13:54 
Titel: Erledigt...
Hallo Luckie

user profile iconLuckie 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