![]() |
Einem Thread Zeit geben
Hallo,
ich habe ein kleines Thread-Problem. Ich starte einen Thread, der eine Liste abarbeitet. Während er dies tut, kann es passieren, dass sich die Liste verändert. Beispielsweise wird die Liste geleert und mit neuen Inhalten gefüllt. Leeren, Füllen und Abarbeiten sind mit CirticalSections geschützt. Bei jedem Neubefüllen wird ein neuer Thread gestartet. Mein Problem ist folgendes: Angenommen, in der Liste sind 100 Elemente. Der Thread arbeitet sich durch die Liste, und nachdem er bei Position 50 angekommen ist, wird die Liste geleert und mit 100 neuen Elementen gefüllt. Der Thread bekommt vom Leeren und Neubefüllen nichts mit und macht mit (dem neuen) Element #51 weiter. Würde der Thread mitbekommen, dass die Liste zwischenzeitlich geleert wurde (eine Abfrage ist im Thread vorhanden), dann könnte sich der aktuelle Thread beenden, und der neu erzeugte arbeitet sich durch die neuen Elemente. Leider kommt der Thread aber zwischen Leeren und Neubefüllen nicht zum Zug. Folglich arbeitet der Thread bis zum Element 100, und danach kommt der neue Thread und fängt wieder bei Eins an. Ich würde also gerne zwischen Leeren und neu Befüllen meinem Thread etwas Zeit abgeben, damit er sich auf die neue Situation einstellen (bzw. sich beenden) kann. Etwa so:
Delphi-Quellcode:
Nur wie sage ich dem Main-Thread, dass jetzt kurz ein anderer zum Zuge kommen soll?
Leere_Liste;
Suspend_MainThread; // <- Hier! Fülle_Liste; Gruß xaromz |
Re: Einem Thread Zeit geben
Zitat:
Terminiere doch den alten Thread, ändere die Daten und starte einen Neuen. Der Thread muss ständig prüfen, ob er nicht schon terminiert wurde:
Delphi-Quellcode:
Der Thread sollte sich ausserdem beim Hauptthread(Anwendung) melden, wenn er mit der Arbeit fertig ist.
procedure TXXXThread.Execute;
begin for i:=0 to liste.Count-1 do begin if Termined then Exit; // <<<==== BearbeiteListenElement(i); end; end; |
Re: Einem Thread Zeit geben
Hallo,
Zitat:
Übrigens weiß die Hauptanwendung gar nicht, dass es da einen Thread gibt :stupid: . Gruß xaromz |
Re: Einem Thread Zeit geben
Hi.
Wenn du das leeren und Befüllen eh schon mit einer Semaphore geschützt hast, kann du dir an der Stelle doch einen Merker setzen, dass geändert worden ist. Der Thread setzt diesen Merker zurück, wenn er anfängt zu arbeiten und fragt ihn vor jedem Schleifenzugriff ab. Sitzt der Merker wieder, schießt sich der Thread selber ab ...
Delphi-Quellcode:
cu
procedure TXXXThread.Execute;
begin Geaendert:=FALSE; for i:=0 to liste.Count-1 do begin if Geaendert then Exit; BearbeiteListenElement(i); end; end; easywk |
Re: Einem Thread Zeit geben
Zitat:
Gruß xaromz |
Re: Einem Thread Zeit geben
Im Prinzip sollte die Abarbeitung so aussehen.
Delphi-Quellcode:
procedure TXXXThread.Execute;
var nIdx: integer; begin nIdx:= 0; repeat try cs.enter; if liste.Count >= nIdx then begin machwas mit liste[nidx]; inc(nIdx); end; // evtl. nIdx auf 0 setzen wenn liste.count < nIdx finally cs.leave; end; until (Idx >= liste.count) or (liste.count = 0) or Application.Terminate; end; |
Re: Einem Thread Zeit geben
Aua aua aua ...
Von der ständigen Neuerstellung eines Threads kann ich nur abraten. Man sollte den einen Thread bei wiederkehrenden Aufgaben immer wiederverwenden. Und dann gehen wir nochmal ein wenig in's Detail in Sachen Threads. Von außen kann man den Thread niemals sicher an einer bestimmten Stelle anhalten [s.u.]. Das sinnvollste wäre also eine Kombination aus einer Liste/Listenklasse welche sich bewußt ist, daß mehrere Threads darauf zugreifen. Ich würde in jedem Fall eine verkettete Liste benutzen. Der Hauptthread sollte sich mit ![]() Sinnvoll wäre desweiteren, wenn der Thread in einer globalen Variablen (oder Member-Variablen) über die Interlocked-Funktion auch den Index des aktuell bearbeiteten Elements festhalten würde. Dann könnte der Thread welcher die Listen befüllt und auch wieder freigibt schonmal alle Elemente bis auf das fragliche wieder freigeben und würde zuguterletzt nur darauf warten dieses letzte Element freizugeben sobald das Ausschlußobjekt wieder frei ist. Nun nochmal zum Anhalten Wenn wir uns vor Augen führen, daß so ziemlich jeder "Delphi-Befehl" in viele (nicht-atomare) Opcodes (also CPU-Befehle) zerlegt wird, dann kann man sich leicht vorstellen, daß wir in "Delphi" folgendes haben (EIP = aktuelle Verarbeitungsstelle des Threads):
Code:
in "Assembler" sähe das dann z.B. so aus:
[color=red]Bla;[/color]
[color=green]Bla2(ListenElement);[/color] [color=blue]Bla3;[/color]
Code:
Die farblich gleichgestalteten Befehle entsprechen einander (HLL<>ASM).
[color=red]XOR ...
MOV ... OR ... MOV ... XCHG ... MOV ... AND ... TEST ... XOR ... MOV ... OR ...[/color] [color=green]MOV ... XCHG ... MOV ... AND ... TEST ... XOR ... MOV ... OR ... MOV ... [u][b]XCHG ...[/b][/u][EIP] MOV ... AND ... TEST ... OR ... MOV ... XCHG ... MOV ... AND ... TEST ... XOR ... MOV ... XOR ...[/color] [color=blue]MOV ... OR ... MOV ... XCHG ... MOV ... AND ... TEST ... XOR ... MOV ... OR ... MOV ... XCHG ... MOV ... AND ... TEST ...[/color] Aber holla, dadurch daß wir den Thread von außerhalb schlafengelegt haben, steht die Ausführung mitten in der Funktion die auf das Listenelement zugreift. Wird das Listenelement also freigegeben, kann dennoch ein Zugriff auf den entsprechenden (nun freigegebenen) Speicherbereich passieren. Mit etwas Glück kommt nur Müll raus, mit etwas Pech schießt sich der Thread selber ab, mit noch mehr Pech landet die Anwendung in den ewigen Jagd...ähem...Ausführungsgründen ... Das was guste geschrieben hat, ist einerseits nicht so dumm, andererseits naiv. Das Problem ist, daß man jedem Listenelement eine eigene Critical Section mitgeben müßte. Tut man's nicht, hat man sogleich den Sinn des Thread ad absurdum geführt ... Anwendung: Nehmen wir die Beispiele von oben, kackt dir der Thread in den meisten Fällen auch ab, wenn EIP mitten in der grün gefärbten Funktion stehenbleibt :shock:
Code:
(Geht bei shmia's Beispiel auch ...)
procedure TXXXThread.Execute;
begin Geaendert:=FALSE; for i:=0 to liste.Count-1 do begin if Geaendert then Exit; [color=green]BearbeiteListenElement(i);[/color] end; end; Tip: Teste dein Prorgramm auch auf einem SMP-System!!! Noch 'ne Idee: Wenn du eine zweifach verkettete Liste nimmst, kann der Thread ein Listenelement ausklinken und später wieder in die Liste einklinken. Wenn du dann immer mit Interlocked-Funktionen arbeitest, kann der "Füll-Thread" das gleiche machen und dein aktuell bearbeitetes Listenelement an den Anfang der Liste stellen. Kommt aber eben drauf an, ob deine Aufgabe zyklisch oder linear ist. |
Re: Einem Thread Zeit geben
Hallo,
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Da ich eine bestimmte Reihenfolge in der Liste habe und die Liste auch immer vollstängig sein muss, funktioniert das so leider nicht. Mein Problem hat sich aber jetzt verlagert (Siehe Posting #5): Wenn ich das Programm beende, während der Thread noch läuft, erhält dieser keine Chance, sich sauber zu beenden. Deshalb möchte ich eben beim Beenden des Programms den Hauptthread kurz schlafen legen, damit der Arbeitsthread auf ein Flag reagieren und sich beenden kann. Ich möchte also Windows sagen, dass jetzt erst mal alle anderen Threads ausgeführt werden sollen (meinetwegen auch ein bestimmter Thread, ich hab das Handle ja), und danach wieder der Hauptthread. Gruß xaromz |
Re: Einem Thread Zeit geben
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Meines Erachtens ist deine Denk- und Herangehensweise zu naiv. Du könntest dir eine Menge Arbeit sparen, wenn jede Liste ein Ausschlußobjekt hat (könnte ein Byte- oder DWORD-Wert sein), der immer nur über die Interlocked-Funktionen bearbeitet wird. Dieses Ausschlußobjekt ist kein echtes Objekt sondern ein Referenzzähler. Da jedes Objekt nun nur noch ein Pointer auf einen Speicherbereich ist, übergibst du dieses an einen Thread nachdem der Aufrufer den Referenzzähler um 1 erhöht hat. Danach kann der Thread erstmal machen was er will, außer daß er davon ausgehen muß, daß die Daten jederzeit von einem anderen Thread gelesen werden können - die Integrität muß also garantiert sein (z.B. kritischer Abschnitt für jede der zu schreibenden Informationen ...). Wenn der (Arbeits-)Thread mit der Abarbeitung fertig ist, legt er sich erstmal selber schlafen (suspend) nachdem er den Referenzzähler um 1 reduziert hat. Dadurch ist er immer an einer wohldefinierten Stelle eingeschlafen. Will der Dispatcher-Thread nun dem Arbeits-Thread sagen, daß er wieder was machen soll, wird der Pointer (globale Variable o.ä.) zum Listeneintrag atomisch ausgetauscht und der Arbeits-Thread wieder aufgeweckt, womit die Schleife im Arbeits-Thread ihre Arbeit weiterführt. Damit ist zwar ein extra Thread (der Dispatcher) nötig, aber die Anzahl der Arbeits-Threads kann ohne Mühe variiert werden. Außerdem mußt du nur einen Listeneintrag in der Listbox aktualisieren, nicht die komplette Liste. Zitat:
|
Re: Einem Thread Zeit geben
Hallo,
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Zitat:
Egal, wie ich den Zugrif steuere (Criticl Section, Interlock-Referenzzähler, Semaphore...), wenn ein Thread läuft, während ich das Programm beende, dann läuft er erstmal weiter und wird dann vermutlich einfach "abgeschossen". Aber ich hab' das gerade so gelöst: - Falls beim Beenden ein Thread läuft, signalisiere ich dem Thread ein Terminate und rufe WaitFor auf - Anschließend werte ich das Flag im Thread aus und setze FreeOnTerminate aus False, damit das Handle in WaitFor erhalten bleibt und keine AV kommt - Sobald WaitFor zurückgekehrt ist, zerstöre ich den Thread mit Free Zitat:
Aber mein Problem ist ja jetzt gelöst. Gruß xaromz |
Alle Zeitangaben in WEZ +1. Es ist jetzt 10:18 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz