Autor |
Beitrag |
FrEEzE2046
      
Beiträge: 109
Windows 98, 2000, XP Pro, Vista Ultimate 32 & 64 Bit, Windows 7 Beta 64 Bit
C/C++, C# (VS 2008 TeamSystem) - Delphi (Delphi 5) - Java (Eclipse)
|
Verfasst: Mo 19.10.09 12:46
Hallo,
ich brauche drigend euere Hilfe  . Ich habe einen Thread der mehrfach gestartet werden muss. Da das eigentliche Programme zu viel Komplexität hat, habe ich das Problem auf ein ganz einfaches Beispiel heruntergebrochen:
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: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81:
| program RTT; {$APPTYPE CONSOLE} uses Windows, sysutils;
type TParam = record PHandle : ^THandle; PInt : ^Integer; end;
var Int : Integer = 0; hThread : THandle; ExitEvent : THandle; dwThreadID : DWORD; Param : TParam; msg : tagMSG; dwStatus : DWORD; bCanceled : Boolean = false;
function ThreadFunc(lpParams: Pointer): Integer; stdcall; var PParam : ^TParam absolute lpParams; begin while not bCanceled do begin Inc(PParam^.PInt^); SetEvent(ExitEvent); SuspendThread(PParam^.PHandle^); writeln('test'); end;
writeln('Thread will be terminated'); ExitThread(0); end;
begin
ExitEvent := CreateEvent(nil, true, false, nil);
hThread := CreateThread( nil, 0, @ThreadFunc, @Param, CREATE_SUSPENDED, dwThreadID );
Param.PHandle := @hThread; Param.PInt := @Int;
while( Int <> 1000 ) do begin ResumeThread(hThread);
while(true) do begin while PeekMessage(msg, 0, 0, 0, PM_REMOVE) do DispatchMessage(msg);
dwStatus := MsgWaitForMultipleObjects(1, ExitEvent, false, INFINITE, QS_ALLINPUT);
if( dwStatus = (WAIT_OBJECT_0 + 1) ) then continue else break; end;
writeln(Int); end;
bCanceled := true; ResumeThread(hThread);
readln; end. |
Leider funktioniert das nicht wirklich. Mal wird eine Zahl doppelt ausgegeben, mal kommt mehrfach hintereinander "test" ... wie kann man das richtig lösen? SuspendThread ist ja mehr für Debugger.
|
|
Lossy eX
      
Beiträge: 1048
Erhaltene Danke: 4
|
Verfasst: Mo 19.10.09 15:42
Du gehst davon aus, dass SuspendThread deinen Thread sofort anhält. Das würde ich nicht tun. SuspendThread oder ResultThread sorgen nur dafür, dass der TaskSheduler von Windows weiß, dass der Thread gestoppt oder gestartet werden soll. Deine Methode läuft aber noch. Erst, wenn der TaskSheduler diese Methode unterbricht (oder sie sich selbst) würde er den Thread schlafen legen. Entsprechend könnte es also durchaus passieren, dass dein Thread mehrere Durchläufe schafft bevor der TaskSheduler ihn schlafen legt. Das würde das wiederholende "Test" erklären.
^THandle, ^Integer und ^TParam solltest du jeweils als eigene Typen deklarieren. Dann kannst du deine Parameter auch einfach Casten und musst nicht mit so etwas wie absolut arbeiten. Folgendes und der Drops ist gelutscht.
Delphi-Quelltext 1: 2: 3: 4: 5: 6:
| type PParam = ^TParam;
function ThreadFunc(lpParams: Pointer): Integer; stdcall; begin Inc(PParam(lpParam)^.PInt^); |
Du verwendest ein Event was manuell zurück gesetzt werden muss (zweiter Parameter). Dieser Status wird also nicht durch ein WaitFor* zurück gesetzt. Entsprechend wird es so sein, dass dein Thread dieses Event ein mal setzt und es immer als gesetzt angezeigt wird. Das erklärt auch warum du doppelte Zahlen siehst. Vermutung: Wenn dein Thread mehr als nur ein Inc machen würde, dann dürfte sich das noch verstärken.
Zu deinen Threads. Es lässt sich anhand deines Beispiels nur schwer sagen was die beste Architektur ist. Wenn die Aufgaben groß genug sind könntest du auch schlicht einen Thread starten und warten bis sich dieser Beendet. Anschließend startest du wieder einen neuen Thread. Wobei das auch nur Sinn macht, wenn du mehr als einen Thread startest.
Ansonsten würde ich eventuell mit mehr als einen Event arbeiten. Also 2 Events pro Thread. Ein Event gibt an, dass der Thread arbeit hat. Darauf muss der Thread warten. Wenn es, von deiner Hauptschleife, gesetzt wurde, dann erledigt der Thread seine Arbeit und setzt ein zweites Event was signalisiert, dass seine Arbeit erledigt ist. Deine Hauptanwendung würde wiederrum auf das Zweite warten. Wärend deine Hauptanwendung also feststellt, dass ein "arbeit erledigt" Event gesetzt wurde muss es sofort neue Arbeit an den Thread reichen und das "Arbeit verfügbar" Event setzen.
Muss ich erwähnen, dass es das Beste wäre, wenn du alle deine threadspezifischen Daten in einer Klasse kappselst und diese Klasse sich dann entsprechend um den Thread kümmert?
_________________ Nur die Menschheit ist arrogant genug, um zu glauben sie sei die einzige intelligente Lebensform im All. Wo nicht mal das nachhaltig bewiesen wurde.
|
|
FrEEzE2046 
      
Beiträge: 109
Windows 98, 2000, XP Pro, Vista Ultimate 32 & 64 Bit, Windows 7 Beta 64 Bit
C/C++, C# (VS 2008 TeamSystem) - Delphi (Delphi 5) - Java (Eclipse)
|
Verfasst: Di 20.10.09 11:47
Hallo,
erst einmal danke für deine Antwort. Ich habe das Problem jetzt in der Tat mit mehreren Events gelöst, stehe jetzt aber vor dem nächsten. Es ist so, dass eine Funktion, die innerhalb des Threads läuft sich rekursiv aufruft. Das führt leider zu einem Stack overflow.
Ich habe versucht, der Funktion CreateThread STACK_SIZE_PARAM_IS_A_RESERVATION als CreationFlag zu übergeben und bei dwStackSize den Wert 8 KiloByte gewählt (also das doppelte vom default Wert). Leider zeigt das überhaupt keine Wirkung.
Die Funktion SetThreadStackGuarantee kann ich leider nicht benutzen, da es auch unter Windows XP 32 Bit laufen muss.
Hast du eine Idee?
|
|
Lossy eX
      
Beiträge: 1048
Erhaltene Danke: 4
|
Verfasst: Di 20.10.09 14:08
Ohne Code kann dir dazu niemand etwas sagen.
Aber Rekursionen sind normal so, dass eine Methode sich selbst aufruft und nach getaner Arbeit sich auch wieder verlässt. Also, dass du wieder in der Hauptmethode landest. Ansonsten müsste man überprüfen was du für Parameter an die Methoden übergibst und was für lokale Variablen existieren bzw auch wie oft du die Methode aufrufst. Wenn da was großes dabei ist, dann sind 8KB recht schnell weg. Ich meine Delphi benutzt für so etwas normal 1MB. Wenn du das flag weg lasst, dann nimmt er die Stackgröße der Anwendung. Was spricht denn da dagegen?
_________________ Nur die Menschheit ist arrogant genug, um zu glauben sie sei die einzige intelligente Lebensform im All. Wo nicht mal das nachhaltig bewiesen wurde.
|
|
FrEEzE2046 
      
Beiträge: 109
Windows 98, 2000, XP Pro, Vista Ultimate 32 & 64 Bit, Windows 7 Beta 64 Bit
C/C++, C# (VS 2008 TeamSystem) - Delphi (Delphi 5) - Java (Eclipse)
|
Verfasst: Mi 21.10.09 13:16
Lossy eX hat folgendes geschrieben : | Ohne Code kann dir dazu niemand etwas sagen.
Aber Rekursionen sind normal so, dass eine Methode sich selbst aufruft und nach getaner Arbeit sich auch wieder verlässt. Also, dass du wieder in der Hauptmethode landest. Ansonsten müsste man überprüfen was du für Parameter an die Methoden übergibst und was für lokale Variablen existieren bzw auch wie oft du die Methode aufrufst. Wenn da was großes dabei ist, dann sind 8KB recht schnell weg. Ich meine Delphi benutzt für so etwas normal 1MB. Wenn du das flag weg lasst, dann nimmt er die Stackgröße der Anwendung. Was spricht denn da dagegen? |
Hallo,
also es ist folgendermaßen:
Ich habe natürlich zu beginn die Default Stackgröße der Anwendung benutzt (implizit, also einfach ohne Definition). Da löste mein Programm immer eine "benutzerdefinierte Exception" aus. Da mir dies absolut keinen Hinweis darauf gibt, wo genau der Fehler aufgetreten ist, habe ich innerhalb des Threads einen try ... except Block eingefügt. Dieser brachte dann die Message "Stacküberlauf" zu Tage.
Daher habe ich eine Möglichkeit gesucht, die Stackgröße zu erhöhen. Vor allem da ich ohnehin auf einen Stacküberlauf getippt hatte (aufgrund der Rekursion). Natürlich baut sich die Rekursion wieder von selber ab, allerdings wird die Funktion auch sehr häufig aufgerufen.
Leider kann ich dir den Source nicht zur Verfügung stellen, die Parameter sind aber lediglich 2 Int64.
Wenn die default Größe bei 1 MB liegen würde, könnte ich die Funktion immerhin 2^16 = 65536 mal aufrufen. In meinen Projektoptionen ist standardmäßig die Rede von:
Minimale Stackgröße = $00004000 = 16 kB = 1024 Aufrufe (hier wird es schon kritisch).
Maximale Stackgröße = $00100000 = 1024 kB = 1 MB
Ich weiß jetzt natürlich nicht, inwiefern und wann er den Stack "vergrößert". Erkennt er über die Notwendigkeit? Soll heißen: Erkennt er, dass hier etwas rekursiv läuft und er daher mehr Stack braucht?
Man darf nicht vergessen, dass die Rechnung, wie ich sie oben gemacht habe, im Prinzip nicht ganz richtig ist. Auf dem Stack werden auch Dinge wie die Rücksprungadresse, sowie Lokalevariablen abgelegt, was natürlich die Anzahl der möglichen Aufrufe verringert. Lokale Variablen sind nochmal 2 Int64 vorhanden und da das Ganze in einer Klasse abläuft, bin ich mir nicht sicher, wie viel er da noch benötigt.
Da ich nicht an der programmeigenen Einstellung ansetzen möchte, würde ich gerne den Stack nur für den Thread vergrößern.
|
|
Lossy eX
      
Beiträge: 1048
Erhaltene Danke: 4
|
Verfasst: Mi 21.10.09 14:05
So wirklich hilft mir das jetzt aber nicht.
Bei Klassen wird noch mal ein unsichtbarer Pointer übergeben. Ansonsten wird der Stack nur durch lokale Variablen und Funktionsparameter beeinflusst. Plus interne Datenstrukturen (Rücksprungadressen). Aber was da an internen genau drinne steckt weiß ich nicht. Try Finally, StackFrames dürften da gut zu Buche schlagen.
Der Stack sollte sich normal selbst erweitern. Müsste sogar Windows machen. Muss aber gestehen, dass ich selbst bisher noch nie so etwas benötigt hatte. Also Rekursionen mit 1000 Aufrufen. Vielleicht solltest du deine rekursive Struktur da auch etwas flacher halten. Also mit einer Liste oder so. Aber ich weiß natürlich nicht was du da machst. Also von daher kann ich so auch nicht wirklich was sinnvolles dazu sagen.
_________________ Nur die Menschheit ist arrogant genug, um zu glauben sie sei die einzige intelligente Lebensform im All. Wo nicht mal das nachhaltig bewiesen wurde.
|
|
|