CreateThread compiliert mit /clr verursacht Handle Leak
-
Hallo,
ich schlage mich seit Tagen mit einem Handle Leak rum. Ich benutze die CreateThread Funktion (_beginthread löst das problem auch nicht) um Threads zu erstellen. Da Teile des Programms managed Code beinhalten (ein Wrapper), muß ich mit der Option /clr kompilieren. Das führt dazu, dass das Programm Handels allokiert, die nicht wieder freigegeben werden, obwohl der Thread richtig beendet wird (EndThread). Es sind genau 5 Handles. Ich habe, um das Problem zu verdeutlichen, folgendes Test Programm geschrieben:
void _stdcall thread_proc(void* arg) { printf("Thread started\n"); Sleep(10000); printf("Thread ends\n"); ExitThread(0); } int _tmain(int argc, _TCHAR* argv[]) { Sleep(10000); printf("call CreateThread"); HANDLE proc = CreateThread(NULL,NULL,(LPTHREAD_START_ROUTINE)thread_proc,NULL,NULL,NULL); Sleep(5000); CloseHandle(proc); getchar(); return 0; }
Ich übergebe also auch keine Variablen, damit fällt dieses Problem raus.
Das Verhalten ist volgendermaßen:
Ohne Compilerflag /clr:
1. Es werden Initial 8 Handles erstellt.
2. Beim Start des Thread wird ein Handle erstellt, um den Thread zu steuern.
3. Der Handle wird über CloseHandle geschlossen.
4. Das Programm hat wie zuvor 8 Handles.Mit Compilerflag /clr:
1. Es werden Initial 83 Handles erstellt.
2. Beim start werden 6 Handles erstellt, einen zum steuern des Threads.
3. Der "SteuerHandle" wird über CloseThread beendet.
4. Obwohl der Thread richtig beendet wurde, bleine die 5 übrigen Handels allokiert.Das Problem ist, dass ich nicht weiß, wie ich diese 5 Handles freigeben kann. Mein Programm, wo ich diese Situation nutze, läuft in einer Endlosschleife und erstellt und beendet Threads kontinuierlich. Das bedeutet, dass irgendwann tausende Handles allokiert sind, und der Prozess aus seinem Speicherbereich läuft "CRASH". Kann mir jemand das verhalten erklären bzw. einen Lösungsansatz geben? Das Problem ist ja leicht nachzuvollziehen. Einfach den oben gegebenen Code in ein Projekt einfügen und mal mit /clr und mal ohne kompilieren. Dann mit dem Process Explorer (microsoft freeware) schauen, wie sich die Handles verhalten.
Meine Entwicklungsumgebung ist Microsoft Visual Studio 2005.
Vielen dank für eure Antworten,Carsten Groth
-
Ich habe im Moment leider kein Rechner zum Testen da, aber was mir in Deinem Code auffällt:
Du erzeugst einen thread der 10 sekunden wartet, zerstörst das Handle auf diesen Thread aber nach 5 Sekunden. Wenn das kein Tippfehler war, schau Dir das noch mal an.
-
Schmeiss ExitThread raus un dnimm einfach nur "return"!!!
-
Hi Leute,
danke für eure antworten.
Leider löst beides das Problem nicht.@Knuddelbaer: Das ist beabsichtigt, hat aber auch keine negativen Konsequenzen. Der Handle kann ruhigen Gewissens zerstötrt werden, da Windows interne Handler benutzt. Der Handler wird nur zurückgegeben, damit der aufrufende Thread/Prozess den erzeugten Thread kontrolieren kann.
@Jochen: Leider löst diese Variante das Problem auch nicht.
Nochmal vielen Dank für die Antworten, habt Ihr noch weitere Ideen?
Gruß,
Carsten
-
Wie hast Du denn das Leak festgestellt???
-
Ich hab das mal laufen lassen und es sah wirklich sehr suspekt nach leak aus.
Es hat aber mit dem GC zu tun.
Ruf einfach mal GC::Collect auf und dann siehst du das es sich normalisiert.
-
@Jochen
Ich habe mich gewundert, warum mein Programm nach ein Paar Stunden das zeitliche segnet, Also habe ich mir mit dem Microsoft Process Explorer den Speicherverbrauch und die Anzahl der Handles anzeigen lassen. Dann ist mir aufgefallen, dass bei jedem CreateThread 5 "suspekte" Handles erstellt werden. Weiterhinn nimmt der Speicherverbrauch kontinuierlich zu.@Gator
Kann ich dass in unmanaged c++ Code aufrufen? wenn ja, welcher include? Gibt es vielleicht noch eine elegantere Lösung?Gruß,
carsten
-
Ich denke man sollte CreateThread überhaupt nicht direkt verwenden.
http://blog.m-ri.de/index.php/2007/11/28/createthread-und-die-crt/
-
Danke für den Post. In meinem Programm benutze ich auch _beginthread(). Allerdings hat das nichts mit meinem Problem zu tun, da beide Funktionen gleich reagieren.
Ich habe, um das Problem zu umgehen, die Funktionen die auf die CLR zugrafen in eine seperate Library ausgelagert. Somit konnte ich den Programmteil, der die Thread erzeugt, ohne /clr kompilieren. Und siehe da, das Leak ist weg.
Allerdings interessiert mich trotzdem, was da schief läuft (reine Neugier). Hat jemand ne Idee, was da genau passiert?
Gruß,
Carsten
-
Ja das konnte ich mir denken das es da nicht vorkommt.
Die IJW invocation wrappt ja in dem Fall deine Funktionsaufrufe in die lib und nicht mehr nur den CreateThread Aufruf.
Ich denke also das es etwas mit der Art und Weise zu tun hat wie das "thunking/marshaling" von CreateThread vonstatten geht. Ich denke sogar das es sich dabei um Managed resources (wrapped) handelt, die aber speziell behandelt werden, und nichtmal in einem "profiler" auftauchen, eben mit Collect, jedoch nicht mit einfachem Speicherdruck, wieder freigegeben werden. (Komisch!)Bezüglich deiner Frage ob und wie man das GC::Collect einbindet, ist die Antwort: Es ist ja mit /clr kompiliert, also hast du vollen Zugriff auf das .net framework. Du brauchst also nur die Reference auf die assembly (System) setzen und GC::Collect aufrufen.
Für mich ist das sehr interessant. Ich habe schon einige "qualifizierte" Leute zu diesem Thema angekurbelt und warte auf deren Feedbacks. (Werde natürlich diesbezüglich diesen Thread weiter mit infos "beposten")
WX
-
Kannst Du mir mal ein Beispielprojekt zukommen lassen?
-
Jochen Kalmbach schrieb:
Kannst Du mir mal ein Beispielprojekt zukommen lassen?
Dazu brauche ich noch die Emailadresse. Schick mir einfach eine E-Mail an thesnoopy@web.de.
-
@Gator
Kann ich dass in unmanaged c++ Code aufrufen? wenn ja, welcher include? Gibt es vielleicht noch eine elegantere Lösung?Nein das musst du mit der using-Klausel machen:
#using<System.dll>
Keine Ahnung ob das noch up-to-date ist