Wie erstellt man einen neuen Thread?



  • Die Verzögerung tritt beim WaitFor() auf. Wenn ich das rausnehme, tritt die Verzögerung beim delete auf. Und ja, die Verzögerung habe ich auch beim einzelnen durchsteppen.

    Einstellung sind die Standardeinstellungen für ein neues Projekt im BCB 6 (respektive genau so, wie in dem hochgeladenen Projekt).
    Wenn ich auf 'Endgültig', ohne Laufzeit-Packages und ohne dynamische RTL kompiliere, verdoppelt sich die Wartezeit fast (auf 12 bis 16 Sekunden). Spontan hätte ich gesagt, dass sich die Zeit eher etwas reduziert?!?

    Mit welcher Version testest Du?



  • C++Builder 6; ich hab es aber auch mit C++Builder 2010 getestet, und es verhält sich da nicht anders. (Windows XP SP3.)

    Wenn du mit Debug-Bibliotheken kompilierst und während der 7s-Verzögerung das Programm unterbrichst, wird der Call-Stack vermutlich auf eine Zeile von TThread.WaitFor verweisen. Welche ist das genau?

    function TThread.WaitFor: LongWord;
    {$IFDEF MSWINDOWS}
    var
      H: THandle;
      WaitResult: Cardinal;
      Msg: TMsg;
    begin
      H := FHandle;
      if GetCurrentThreadID = MainThreadID then
      begin
        WaitResult := 0;
        repeat
          { This prevents a potential deadlock if the background thread
            does a SendMessage to the foreground thread }
          if WaitResult = WAIT_OBJECT_0 + 1 then
            PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE);
          Sleep(0);
          CheckSynchronize; // <-- das hier?
          WaitResult := MsgWaitForMultipleObjects(1, H, False, 0, QS_SENDMESSAGE); // <-- oder das hier?
          Win32Check(WaitResult <> WAIT_FAILED);
        until WaitResult = WAIT_OBJECT_0;
      end else WaitForSingleObject(H, INFINITE); // <-- oder gar das hier? (wäre seltsam, da ich GetCurrentThreadID = MainThreadID erwarten würde)
      CheckThreadError(GetExitCodeThread(H, Result));
    end;
    


  • In diesem Forum gibt es anscheinend ein Tutorial für Threads:

    http://bcb-tutorial.c-plusplus.net/Thread/artikel5.html

    Ich glaube da ist ein Fehler drin.
    Dort wird im Thread-Konstruktor FreeOnTerminate auf true gesetzt. Das bedeutet doch, dass der Thread nach der Ausführung von "Execute" sich selbstständig terminiert. Und in der Cleanup-Funktion wird "Terminate()" aufgerufen. Das ist doch überflüssig, oder?



  • @sonic:
    Ich habe mir das Tutorial jetzt nicht angesehen, aber der Aufruf von Terminate() beendet einen Thread. Wenn FreeOnTerminate=true ist, muss man weiter nichts machen. Wenn es false ist, muss man den Thread auch noch löschen.
    Der Aufruf von Terminate() ist nicht grundsätzlich verboten, auch wenn der Thread mit FreeOnTerminate=true ist. Es gibt Threads die laufen in einer kontunierlichen Schleife, diese muss man mit Terminate() beenden. Wenn ein Thread allerdings eine eigene Abbruchbedingung hat, sollte man Terminate() nicht verwenden.

    @audacia
    Der bleibt in der Zeile 19, also dem WaitResult := ... stehen. Zumindest ist das die aufrufende Funktion. Viel häufiger wird mir allerdings der Aufruf von Sleep als aufrufende Funktion angezeigt (in 9 von 10 Fällen).
    Das verstehe ich dann jetzt überhaupt nicht. Ich mein, es ist nur ein 200 ms Sleep, wie kann das mehrere Sekunden beanspruchen?!? Könnte das an meiner Hardware liegen? Der Rechner ist zugegebenermaßen schon ziemlich alt.



  • Joe_M. schrieb:

    @audacia
    Der bleibt in der Zeile 19, also dem WaitResult := ... stehen. Zumindest ist das die aufrufende Funktion. Viel häufiger wird mir allerdings der Aufruf von Sleep als aufrufende Funktion angezeigt (in 9 von 10 Fällen).
    Das verstehe ich dann jetzt überhaupt nicht. Ich mein, es ist nur ein 200 ms Sleep, wie kann das mehrere Sekunden beanspruchen?!? Könnte das an meiner Hardware liegen? Der Rechner ist zugegebenermaßen schon ziemlich alt.

    Wir hatten bei einem Projekt genau das gleiche Verhalten, nur dass es nicht 7 Sekunden, sondern 30+ waren. Das Szenario war das gleiche, im Destruktor des TThreads blieb die CPU auf WaitFor stehen. Das Verhalten war nicht immer 100%ig (eigentlich war das korrekte Verhalten nicht reproduzierbar, in 4 von 5 Fällen hing der Thread im Destruktor) reproduzierbar, es trat sowohl auf zwei Entwicklungsrechnern als auch auf einem Produktionssystem auf.



  • Hallo

    Das Problem mit dem blockieren im WaitFor des Threads liegt darin, dass WaitFor intern einen Sleep(0) verwendet. Dieser gibt den Prozessor nur an Threads mit gleicher Priorität ab.
    Wenn auf einen Thread mit tieferer Priorität gewartet wird, erhält dieser keine CPU-Zeit (ausser auf Mehr-Prozessor-Systemen).
    Zur Abhilfe sollte die Priorität des Threads, auf den gewartet wird vor dem WaitFor (oder vor Terminate) auf mindestens die Priorität des aktuellen Threads angehoben werden.

    Gruss Chis



  • Ich bin sprachlos. Mit einem Priority = tpHigher vor der Terminate()-Aufruf, ist das Problem tatsächlich weg. Was hab ich damals nicht alles probiert, aber darauf bin ich nicht gekommen...

    Aber im Beispiel zu Priority wird die Priorität gesetzt, bevor der Thread mit Resume fortgesetzt ist. Ist das nur Zufall, oder soll das ein Hinweis darauf sein, dass man die Priorität zur Laufzeit nicht ändern sollte?



  • Ich habe schon öfters Thread-Prioritäten zur Laufzeit geändert. Konnte bisher nie irgendein Nachteil feststellen. In der Doku habe ich auch keine Hinweise gefunden, die dies als schlecht einstufen.

    Gruss Chris



  • Chrisi_K schrieb:

    Das Problem mit dem blockieren im WaitFor des Threads liegt darin, dass WaitFor intern einen Sleep(0) verwendet. Dieser gibt den Prozessor nur an Threads mit gleicher Priorität ab.
    Wenn auf einen Thread mit tieferer Priorität gewartet wird, erhält dieser keine CPU-Zeit (ausser auf Mehr-Prozessor-Systemen).

    Tatsächlich - ist ja auch so in der Dokumentation zu Sleep() nachlesbar. *facepalm*

    Zu dem Problem gibt es auch schon einen QC-Report: #3260. Allerdings sollte das Problem mittlerweile nicht mehr auftreten, denn in C++Builder 2006 und höher verwendet TThread.WaitFor kein Sleep(0) mehr:

    function TThread.WaitFor: LongWord;
    {$IFDEF MSWINDOWS}
    var
      H: array[0..1] of THandle;
      WaitResult: Cardinal;
      Msg: TMsg;
    begin
      H[0] := FHandle;
      if GetCurrentThreadID = MainThreadID then
      begin
        WaitResult := 0;
        H[1] := SyncEvent;
        repeat
          { This prevents a potential deadlock if the background thread
            does a SendMessage to the foreground thread }
          if WaitResult = WAIT_OBJECT_0 + 2 then
            PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE);
          WaitResult := MsgWaitForMultipleObjects(2, H, False, 1000, QS_SENDMESSAGE);
          CheckThreadError(WaitResult <> WAIT_FAILED);
          if WaitResult = WAIT_OBJECT_0 + 1 then
            CheckSynchronize;
        until WaitResult = WAIT_OBJECT_0;
      end else WaitForSingleObject(H[0], INFINITE);
      CheckThreadError(GetExitCodeThread(H[0], Result));
    end;
    {$ENDIF}
    

    Das erklärt zwar nicht, weshalb ich das Problem mit C++Builder 6 nicht reproduzieren kann (SetProcessAffinityMask() habe ich natürlich getestet), aber in neueren Versionen sollte es nicht mehr auftreten.



  • audacia schrieb:

    Das erklärt zwar nicht, weshalb ich das Problem mit C++Builder 6 nicht reproduzieren kann (SetProcessAffinityMask() habe ich natürlich getestet), aber in neueren Versionen sollte es nicht mehr auftreten.

    Hast Du mehr als einen CPU-Kern im Rechner?



  • Zwei, sonst wäre SetProcessAffinityMask() redundant 😉
    Das wird sicherlich auch die Ursache sein; allerdings ist mir noch nicht ganz klar, weshalb sich das Problem nicht mit SetProcessAffinityMask() reproduzieren läßt. (Vielleicht arbeitet der Thread-Scheduler auf Mehrkernsystemen grundsätzlich anders? Mal in Windows Internals nachlesen.)



  • Hallo,

    ich habe noch eine Frage zum Thema Thread-Erstellung.
    Ich habe einen Thread, in dessen Execute-Funktion ein Button aufgerufen wird.
    Die Funktion in diesem Button schreibt Daten in eine TStringGrid rein. In der OnDrawCell von dieser StringGrid
    färbe ich die Zellen abhängig von den Zelleninhalten.
    Jetzt kriege ich sporadisch Fehlermeldungen "Zugriffsverletzung an der Adresse xxx" oder "Falsche Zeigeroperation". Der Fehler wird durch
    den Zugriff auf den Zelleninhalt ausgelöst. Das habe ich mit dem Debugger geprüft.

    Kann es sein, dass die OnDrawCell() mit meinem Thread irgendwie synchronisiert werden muss? Weil sie ja im Hauptthread
    aufgerufen wird. Oder die StringGrid ist nicht Thread-sicher



  • Kann es sein, daß du den Hinweiskommentar, den der Thread-Wizard erstellt, nicht gelesen hast?

    // Wichtig: Methoden und Eigenschaften von Objekten der VCL können nur
    // in Methoden verwendet werden, die mit Synchronize aufgerufen wurden, z.B.:
    //
    //      Synchronize(&UpdateCaption);
    //
    // wobei UpdateCaption so aussehen könnte:
    //
    //      void __fastcall TMyThread::UpdateCaption()
    //      {
    //        Form1->Caption = "In Thread aktualisiert";
    //      }
    


  • Ok, vielen Dank.


Anmelden zum Antworten