Was geht und was geht nicht bei Multithreading



  • Hallo zusammen,

    ich beschäftige mich seit ein paar Tagen mit einer Anwendung die mehrere Threads für bestimmte Aufgaben verwendet.

    Die Anwendung hat eine GUI auf welcher der aktuelle Status und die Aufgaben eines jeden Threads angezeigt werden.
    Das läuft bisher auch ganz gut, leider bekomme ich bei längerer Laufzeit immer wieder Zugriffsverletzungen, leider ist das ganze kaum reproduzierbar. Der Main Threads kommuniziert auf folgende Weise mit den anderen:

    Der Main-Thread hat ein TimerEvent, das alle 5 Sekunden aufgerufen wird, woraufhin er alle anderen Threads auf ihren Verarbeitungstatus abfragt. Jeder Thread ist durch eine Klasse gekapselt, die einen BOOL "Suspended" hat.
    Ist der Thread "suspended" (das macht er selbst wenn er für den Moment fertig ist) wird er dann mit Resume fortgesetzt.
    Der Status des Threads wird dann entsprechend in einem ListView des GUI angezeigt.
    Ist ein Thread noch beschäftigt wird über eine Reihe von public-Variablen der Thread-Klasse die Anzahl der zu verarbeitenden Datensätze und ( Feldlänge ergibt sich aus der Anzahl) die Namen der Datensätze im GUI angezeigt.
    Zum besseren Debuggen gibt es auch ein globale String-Variable "LastAction" in der alle Threads reinschreiben was sie gerade machen, z.B. "Copying file XxX".

    1. Kann eine Thread wirklich an _jeder_ Stelle unterbrochen werden? Also z.B. kann es sein das in LastAction ein unvollständiger String steht weil der Thread der gerade dort reingeschrieben hat, mittendrin unterbrochen wurde. Kann es da sogar schon eine Exception geben?
    2. Die Liste von Datensätzen die ich für jeden Thread Abfrage ist dynamisch. Ich achte darauf das Integer der die Listengröße angibt als letztes inkrementiert wird damit nicht auf noch nicht allozierten Speicher zugegriffen wird. Könnte trotzdem noch etwas schiefgehen?
    3. Einige Threads teilen sich eine Datenbankverbindung. Um Konflikte zu vermeiden habe ich deswegen eine CriticalSection um Zugriffe auf die Datenbankverbindung gemacht. Ist das richtig so? Wäre das auch eine Lösung wenn ich statt public-Variablen set/get Methoden implementiere die sicherstellen das der Status eines Threads nur dann abgefragt wird, wenn nix schiefgehen kann?

    Das ist meine erste Anwendung in der ich massiv Multithreading benutze. Bin gerade am Erfahrung sammeln. Könnte eventuell jemand aufzeigen was ich falsch mache bzw. wie man es richtig macht und was sonst noch Anfängerfehler in diesem Zusammenhang sind?

    Danke!



  • illuminator schrieb:

    1. Kann eine Thread wirklich an _jeder_ Stelle unterbrochen werden? Also z.B. kann es sein das in LastAction ein unvollständiger String steht weil der Thread der gerade dort reingeschrieben hat, mittendrin unterbrochen wurde. Kann es da sogar schon eine Exception geben?

    Ja, ein Thread kann jederzeit unterbrochen werden, auch während einer Exceptionbehandlung. Durch das Unterbrechen selbst kann es zu einer Exception kommen, falls der String inkonsistent ist und ein zweiter Thread beginnt darauf zuzugreifen.

    illuminator schrieb:

    2. Die Liste von Datensätzen die ich für jeden Thread Abfrage ist dynamisch. Ich achte darauf das Integer der die Listengröße angibt als letztes inkrementiert wird damit nicht auf noch nicht allozierten Speicher zugegriffen wird. Könnte trotzdem noch etwas schiefgehen?

    Ja, falls zwei Threads schreibend auf die Liste zugreifen. Außerdem bin ich mir gerade nicht ganz sicher, ob ein C++-Compiler Anweisungen, die ihm unabhängig voneinander erscheinen, nicht umordnen darf.

    Außerdem z.B. folgendes Szenario: Thread A fragt die Größe der Liste ab, bekommt "2". Thread A wird unterbrochen, Thread B löscht ein Element aus der Liste. Thread A kommt wieder dran, geht aber immer noch von 2 Elementen aus.

    illuminator schrieb:

    3. Einige Threads teilen sich eine Datenbankverbindung. Um Konflikte zu vermeiden habe ich deswegen eine CriticalSection um Zugriffe auf die Datenbankverbindung gemacht. Ist das richtig so? Wäre das auch eine Lösung wenn ich statt public-Variablen set/get Methoden implementiere die sicherstellen das der Status eines Threads nur dann abgefragt wird, wenn nix schiefgehen kann?

    IMHO ist eine CriticalSection auf jeden Fall nicht falsch. Public-Variablen würde ich aber auf jeden Fall vermeiden, da darauf ja jederzeit ohne CriticalSection zugegriffen werden kann. Die db-Klasse sollte besser ein paar Methoden anbieten, um mit der db kommunizieren zu können (müssen keine get/set-Methoden sein, wenn man das Interface schöner abstrahieren kann); diese Methoden rufen dann intern die CriticalSection-Funktionen auf (das CS-Objekt ist auch nur klassenintern erreichbar). Gilt natürlich auch für die Threadklassen.

    illuminator schrieb:

    Das läuft bisher auch ganz gut, leider bekomme ich bei längerer Laufzeit immer wieder Zugriffsverletzungen, leider ist das ganze kaum reproduzierbar.

    Hast du die Anwendung schon im Debugger gestartet und dort länger laufen lassen?



  • Also, Dein Fehler liegt daran, daß Du in zwei Threads ab und zu ohne Synchronisation auf eine komplexere Datenstruktur zugreifst. Dadurch liest der eine Thread bereits, während der andere noch schreibt - bumm. Nicht reproduzierbar ist es, weil das rein stochastisch ist, ein reiner Zeiteffekt (je länger es läuft, desto eher nähert sich die Wahrscheinlichkeit für das Ereignis 100%).

    1. Kann so unterbrochen werden. Die Unterbrechung geschieht auf Assemblerebene, d.h. es wird keine Rücksicht auf den Zustand einer Hochsprachenanweisung genommen. Innerhalb des Threads kann es dadurch keine Exception geben, da der Thread ja genau am alten Zustand fortsetzt. Greift aber nun ein anderer Thread auf den String zu ist dieser in einem illegalen Zustand. Lösung: jedes Objekt, auf das ein Thread schreibend zugreifen darf während (mind 1) anderer Thread liest, muß durch eine Criticalsection geschützt werden.

    2. siehe 1, ja, kann. Die komplette Listenoperation muß geschützt werden, bevor ein anderer an die Liste ran darf. Eine reine Variation der Reihenfolge ist nicht ausreichend, dadurch ist kein Schutz vor dem Problem möglich. Es muß einen Leselock geben, bis der Schreibvorgang komplett abgeschlossen ist und die Datenstruktur in einem definierten Zustand ist.

    3. Klingt grundsätzlich richtig. Es wäre aber auch denkbar, daß jeder Thread eine eigenen Connection bekommt. Ist es eine Connection, so muß der Zugriff synchronisiert werden. Es gibt keine public-Variablen. Übrigens aus genau diesem Grund: weil man dann Zugriffe nicht synchronisieren kann! Man kann aber einem Objekt sehr schön einen Criticalsection-Member verpassen, über den dann die get/set gelockt werden.

    Siehe dazu:
    http://www.c-plusplus.net/cms/modules.php?op=modload&name=mbBooks&file=index&func=isbn&isbn=3826609891



  • Hallo,

    danke euch beiden.

    Ich hab mir jetzt folgendes überlegt:
    Die Thread-Klassen bekommen 2 neue Methoden: CSEnter und CSLeave
    Jedes mal wenn der Thread selbst oder eine Klasse von außen auf gemeinsam genutze Member der Klasse zureifen wollen müssen sie vorher vorher Enter und danach Leave aufrufen (für die CriticalSection des ThreadObjekts).
    So kann ich, denke ich, verhindern, dass es da Zugriffskonflikte gibt. Ich muss nur in meinem Code sicherstellen, dass alle Zugriffe innerhalb eines Enter und eines Leave stattfinden.

    Oder überseh ich was?



  • illuminator schrieb:

    So kann ich, denke ich, verhindern, dass es da Zugriffskonflikte gibt. Ich muss nur in meinem Code sicherstellen, dass alle Zugriffe innerhalb eines Enter und eines Leave stattfinden.

    Oder überseh ich was?

    Damit rufst du aber doch auch nur wieder eine weitere Methode auf.
    Der Thread A kann ja immer noch angehalten werden und der Thread B ruft dann CSEnter() auf.

    Da brauchste was vom OS. Einen Mutex oder einen Semaphor.



  • Kannst Du machen, ich würde es aber bevorzugen die CriticalSection in die Datenklasse zu verlegen. Denn wenn Du den Thread lockst, sind Deine Sperren zu groß. Das Objekt ist ja die Einheit, die gesperrt werden soll.

    Siehe dazu diesen (sehr alten) Thread, der ein paar Ideen dazu enthält. Für mein Beispiel darin kannst Du z.B. aus der MFC CCriticalSection verwenden, das Beispiel hier war mehr für Linux.

    http://www.c-plusplus.net/forum/viewtopic-var-t-is-39502-and-start-is-0-and-postdays-is-0-and-postorder-is-asc-and-highlight-is-.html



  • Hallo,

    ich hab noch eine Frage.
    Erstmal: Es läuft bis jetzt alles ganz gut, auch wenn die finalen Tests noch ausstehen. Da ist auch schon der Knackpunkt. Ich habe die Anwendung bisher nur auf meinem Notebook getestet. Das Ziel-System, wo dem demnächst auch die finalen Tests stattfinden, ist jedoch eine Dual-Prozessor Maschine mit Win2000 Server.
    Kann das noch irgendwelche Auswirkungen haben, die bisher nicht zum tragen kamen, wenn das Produktivsystem nun "echtes" Multithreading unterstützt?



  • Kann es.

    Durch ein echtes - hardwarebasiertes - Multithreading werden die parallelen Abläufe häufiger auf korrekt funktionierende Synchronisation geprüft - d.h. angenommen Dein bisheriges System hätte einen stochastischen Fehler alle 3 Tage erzeugt, kann er nun alle 4 Stunden auftreten - einfach weil nun mehr potentielle Fehlerquellen abgerufen werden.

    Funktioniert aber alles perfekt, wird es das auch auf dem anderen System tun.


Anmelden zum Antworten