Wie erstellt man einen neuen Thread?



  • Hallo Leute,

    ich möchte in einer Anwendung, die ich mit in BCB 5.0 Pro erstellt habe, einen Thread erstellen.
    Gibt es hier, im Forum, eine Anleitung dazu? Ich finde nichts dazu



  • Datei, Neu, Thread-Objekt.

    Details stehen in der Hilfe; wenn du noch nicht vertraut mit Multithreading bist, lies ein gutes Tutorial zum Thema, etwa dieses hier.



  • Vielen Dank



  • Wenn das ein reiner Worker Thread ohne Interaktion mit der Benutzeroberfläche sein soll würde ich lieber Win32 Threads benutzen. Die aus der VCL haben so komische Randbedingungen, die erfüllt sein müssen (z.B. beim Stoppen). Die dazu notwendigen Funktionen heissen CreateThread, SuspendThread, ResumeThread und TerminateThread.



  • Also ich habe jetzt was zusammengebaut. In einer Timerfunktion erstelle ich alle 100 ms einen neuen Thread. Es läuft auch eine Zeit lang gut. Nach etwa 2 Minuten wird das Programm immer langsamer, und dann kommt die Fehlermeldung "Abnormal termination".
    Ich denke, dass die erstellten Threads nicht richtig beendet werden.

    Weiss vielleicht jemand von euch, was ich falsch gemacht habe?

    Frame_Auswertung *Frameauswertung;
    
    void __fastcall TForm1::TimerEmpfangene_Daten_auswertenTimer(
          TObject *Sender)
    {
    
     // Neuen Thread für die Auswertung starten
     // =======================================
     Frameauswertung = new Frame_Auswertung(false);
    
     // Funktion zuweisen:
     Frameauswertung->OnTerminate = DeleteThread;
    
     Frameauswertung->FreeOnTerminate = true;
    }
    
    void __fastcall TForm1::DeleteThread(TObject *Sender)
    {
     Frameauswertung = NULL;
    }
    


  • DocShoe schrieb:

    Die aus der VCL haben so komische Randbedingungen, die erfüllt sein müssen (z.B. beim Stoppen).

    Was meinst du?
    Für einen Worker-Thread ist das okay, aber ansonsten ist der Verzicht auf Synchronize() und Queue() IMO unangenehm.

    DocShoe schrieb:

    Die dazu notwendigen Funktionen heissen CreateThread, SuspendThread, ResumeThread und TerminateThread.

    Da muß ich gleich dreimal entschieden widersprechen.

    • CreateThread sollte in C- und C++-Programmen nicht verwendet werden, da die C-RTL threadweise initialisiert werden muß. Darauf weist unter anderem die Dokumentation im MSDN hin:

    MSDN schrieb:

    A thread in an executable that calls the C run-time library (CRT) should use the _beginthreadex and _endthreadex functions for thread management rather than CreateThread and ExitThread; this requires the use of the multi-threaded version of the CRT. If a thread created using CreateThread calls the CRT, the CRT may terminate the process in low-memory conditions.

    Das ist in der C++Builder-RTL nicht anders.

    • Der einzige legitime Grund zur Verwendung von ResumeThread ist das Starten eines mit CREATE_SUSPENDED erstellten Threads. Ansonsten ist die Verwendung von SuspendThread und ResumeThread dringend zu vermeiden, denn das kann zu völlig unvorhersehbaren Deadlocks führen, wenn der Thread gerade ein Lock auf irgendeine Ressource hält. Auch das ist im MSDN nachzulesen:

    MSDN schrieb:

    This function is primarily designed for use by debuggers. It is not intended to be used for thread synchronization. Calling SuspendThread on a thread that owns a synchronization object, such as a mutex or critical section, can lead to a deadlock if the calling thread tries to obtain a synchronization object owned by a suspended thread. To avoid this situation, a thread within an application that is not a debugger should signal the other thread to suspend itself. The target thread must be designed to watch for this signal and respond appropriately.

    • Schließlich ist TerminateThread mit Sicherheit von allen Lösungen, einen Thread zu beenden, die schlechteste. Du findest dazu auch etwa auf StackOverflow Hinweise, aber auch hier ist MSDN sehr explizit:

    MSDN schrieb:

    TerminateThread is used to cause a thread to exit. When this occurs, the target thread has no chance to execute any user-mode code. DLLs attached to the thread are not notified that the thread is terminating. The system frees the thread's initial stack.

    Windows Server 2003 and Windows XP/2000: The target thread's initial stack is not freed, causing a resource leak.

    TerminateThread is a dangerous function that should only be used in the most extreme cases. You should call TerminateThread only if you know exactly what the target thread is doing, and you control all of the code that the target thread could possibly be running at the time of the termination. For example, TerminateThread can result in the following problems:

    • If the target thread owns a critical section, the critical section will not be released.
    • If the target thread is allocating memory from the heap, the heap lock will not be released.
    • If the target thread is executing certain kernel32 calls when it is terminated, the kernel32 state for the thread's process could be inconsistent.
    • If the target thread is manipulating the global state of a shared DLL, the state of the DLL could be destroyed, affecting other users of the DLL.

    Die richtigen Alternativen sind _beginthread()/_beginthreadex(), die Event-Funktionen im Windows-API sowie ein derartiges Thread-Design, daß der gewünschte Abbruch durch ein Event signalisiert werden kann. Oder, wenn du es einfach haben willst, einfach ein Wrapper wie TThread.



  • sonic_1233 schrieb:

    In einer Timerfunktion erstelle ich alle 100 ms einen neuen Thread.[...]Weiss vielleicht jemand von euch, was ich falsch gemacht habe?

    Du hast das Tutorial nicht durchgelesen.

    Wie im MSDN einzusehen ist, wird für jeden erstellten Thread etwa 1 MB für den Stack bereitgestellt. Das macht bei dir etwa 10 MB pro Sekunde. Jetzt noch einkalkuliert, daß eine gewöhnliche 32-Bit-Anwendung maximal knapp 2 GB virtuellen Arbeitsspeicher zugewiesen bekommen kann, und du kannst ausrechnen, daß deine Anwendung nach spätestens 3:41 Minuten keinen Adreßraum mehr zur Verfügung hat. In der Realität tritt das natürlich früher ein, weil der Adreßraum ja nicht nur für deine Threads da ist.

    MSDN schrieb:

    The number of threads a process can create is limited by the available virtual memory. By default, every thread has one megabyte of stack space. Therefore, you can create at most 2,048 threads. If you reduce the default stack size, you can create more threads. However, your application will have better performance if you create one thread per processor and build queues of requests for which the application maintains the context information. A thread would process all requests in a queue before processing requests in the next queue.

    Anders gesagt, erstelle einen einzigen Thread, der auf Anweisungen wartet (WaitForSingleObject), sie abarbeitet, dem UI-Thread mittels TThrea::Synchronize() das Ergebnis übergibt und dann von vorne anfängt.

    Es ist nicht sinnvoll, mit Threads einfach so draufloszuprogrammieren, ohne sich zuvor informiert zu haben. Multithreading gehört mit zu den komplexesten Systemabläufen, mit denen sich ein Anwendungsprogrammierer auseinandersetzen muß; wenn du dich nicht gut vorbereitest, wirst du nie in der Lage sein, die unvermeidlichen multithreading-verursachten Abstürze in deiner Anwendung zu beheben. Also reiß dich zusammen und arbeite das Tutorial durch, bevor du fortfährst.



  • audacia schrieb:

    Also reiß dich zusammen und arbeite das Tutorial durch, bevor du fortfährst.

    Ach was, wann hat man sonst die Möglichkeit, so schöne Fehlermeldungen, wie 'Leinwand erlaubt kein zeichnen' zu erhalten. 😉

    Ich hab mit den VCL-Threads aber auch so meine Probleme. Ich hab keine Möglichkeit gefunden, laufende Threads, mit VCL-Mitteln, sinnvoll zu beenden, wenn der User die Anwendung beendet. Da muß man tatsächlich auf Events und WaitForXXX zurückgreifen. Oder ich hab mich wieder zu blöd angestellt...

    Gruß KK



  • Ich habe das Thema wohl auf die leichte Schulter genommen. Aber nur, weil
    mein Chef alles am besten gestern fertig haben will.
    Danke für alle Kommentare. Ich werde gleich das Tutorial durchlesen.



  • @Audacia
    Ich habe nur die 4 wichtigsten Funktionen zur Win32 Threadverwaltung genannt, vielleicht bin ich fälschlicherweise davon ausgegangen, dass der OP sich mit Thread Design und Verhinderung von race conditions auskennt (die man im übrigen auch mit TThread haben kann). Ich stimme dir insofern zu, dass man die Techniken zur Threadsynchronisation beherrschen muss, um mit den Win32 API Funktionen ein robustes Thread Design zu erstellen.

    Das Verhalten eines von TThread abgeleiteten Threads kann mitunter schon seltsam sein, insbesondere wenn ein Thread gestoppt werden soll, der noch nie gelaufen ist. Im Destruktor ~TThread() wird allerhand Synchroniasationszeugs getrieben, wobei es bei uns schon bis zu 30 Sekunden gedauert hat, bis die CPU aus dem Destruktoraufruf zurückgekommen ist. Leider konnten wir die genauen Randbedingungen nie vollständig ermitteln, aber ein Wechsel von TThread auf Threads durch die Win32 API hat das Problem instantan gelöst.
    Wie du schon erwähntest steht man dann aber vor dem Problem, dass man aus den Win32 Threads keine GUI Aktualisierung anstossen kann (jedenfalls nicht durch direkte Methodenaufrufe irgendwelcher VCL Objekte), da die VCL kein Multithreading beherrscht und von sich aus auch keine Hilfsmitteln anbietet, um sich mit nicht VCL-Threads zu synchronisieren. Ich denke aber, dass man eine Synchronisation mit benutzerdefinierten Nachrichten und PostMessage hinbekommt.



  • sonic_1233 schrieb:

    Aber nur, weil mein Chef alles am besten gestern fertig haben will.

    Das ist eigentlich immer der Fall, und nie eine gute Ausrede für laxes Vorgehen 😉

    DocShoe schrieb:

    Ich habe nur die 4 wichtigsten Funktionen zur Win32 Threadverwaltung genannt

    Nein, leider nicht. Du hast vier Funktionen genannt, die man allesamt, mit der Ausnahme von ResumeThread speziell für CREATE_SUSPENDED, tunlichst meiden sollte 😉

    Die wichtigsten Funktionen zur Win32-Threadverwaltung heißen _beginthread[ex](), _endthread[ex](), WaitForSingleObject und Create/SetEvent/ResetEvent.

    DocShoe schrieb:

    Im Destruktor ~TThread() wird allerhand Synchroniasationszeugs getrieben, wobei es bei uns schon bis zu 30 Sekunden gedauert hat, bis die CPU aus dem Destruktoraufruf zurückgekommen ist. Leider konnten wir die genauen Randbedingungen nie vollständig ermitteln

    TThread.Destroy sieht so aus:

    destructor TThread.Destroy;
    begin
      if (FThreadID <> 0) and not FFinished and not FExternalThread then
      begin
        Terminate;
        if FCreateSuspended then
          Resume;
        WaitFor;
      end;
      RemoveQueuedEvents(Self);
    {$IFDEF MSWINDOWS}
      if (FHandle <> 0) and not FExternalThread then CloseHandle(FHandle);
    {$ENDIF}
    {$IFDEF LINUX}
      // This final check is to ensure that even if the thread was never waited on
      // its resources will be freed.
      if (FThreadID <> 0) and not FExternalThread then pthread_detach(FThreadID);
      sem_destroy(FCreateSuspendedSem);
    {$ENDIF}
      inherited Destroy;
      FFatalException.Free;
    end;
    

    Die einzige Stelle, an der realistisch gesehen 30s-Verzögerungen entstehen können ist "WaitFor;" - und das hängt natürlich davon ab, was in deiner Execute()-Methode passiert. Wenn du da nicht gelegentlich überprüfst, ob das Terminate-Flag gesetzt ist, kanns natürlich lange dauern.

    DocShoe schrieb:

    Wie du schon erwähntest steht man dann aber vor dem Problem, dass man aus den Win32 Threads keine GUI Aktualisierung anstossen kann (jedenfalls nicht durch direkte Methodenaufrufe irgendwelcher VCL Objekte), da die VCL kein Multithreading beherrscht und von sich aus auch keine Hilfsmitteln anbietet, um sich mit nicht VCL-Threads zu synchronisieren. Ich denke aber, dass man eine Synchronisation mit benutzerdefinierten Nachrichten und PostMessage hinbekommt.

    Klar, irgendwie möglich ist es schon - warum einfach, wenns auch umständlich geht 😉



  • audacia schrieb:

    Die einzige Stelle, an der realistisch gesehen 30s-Verzögerungen entstehen können ist "WaitFor;" - und das hängt natürlich davon ab, was in deiner Execute()-Methode passiert. Wenn du da nicht gelegentlich überprüfst, ob das Terminate-Flag gesetzt ist, kanns natürlich lange dauern.

    Ich hatte das gleiche Problem: Klick mich
    Wenn Du Zeit und Muße hast da mal reinzuschauen... Vielleicht kannst Du mir ja sagen, was ich da falsch gemacht habe.



  • Joe_M. schrieb:

    Wenn Du Zeit und Muße hast da mal reinzuschauen... Vielleicht kannst Du mir ja sagen, was ich da falsch gemacht habe.

    Zur Situation mit FreeOnTerminate=true kann ich sagen, daß dein Ansatz dann nicht ganz sicher ist:

    __fastcall TForm1::~TForm1(void)
    {
        FMyThread->Terminate(); // -> der Thread kann ab sofort jederzeit terminiert
                                // werden
        FMyThread->WaitFor(); // ... und was, wenn das hier schon passiert ist? Dann
                              // hat sich das Objekt bereits selbst gelöscht - fatal.
        delete FMyThread; // Noch viel schlimmer - wir zerstören es zweimal.
        FMyThread = NULL;
    }
    

    FreeOnTerminate und Terminate()/WaitFor() schließen einander praktisch aus. Mehr noch: wenn ein Thread FreeOnTerminate=true verwendet, darfst du, sobald er läuft, eigentlich nicht mehr von außen auf das Thread-Objekt zugreifen - du weißt nie, ob es überhaupt noch existiert. In diesem Fall ist es notwendig, sich auf andere Kommunikationsmechanismen zu beschränken.

    Selbst wenn du weißt, was der Thread macht und ausschließen kannst, daß er terminiert, bevor du Terminate() von außen aufgerufen hast, so darfst du nach dem Aufruf von Terminate() das Objekt nicht mehr benutzen, was auch ein WaitFor() ausschließt (derartige Versuche werden auch üblicherweise mit einer Exception quittiert). Infolgedessen kann dein Hauptthread nicht warten, bis der Hintergrundthread ordentlich aufgeräumt hat - und da die Beendigung des Hauptthreads auch den Prozeß beendet, wird der Hintergrundthread infolgedessen von Windows terminiert.

    Außerdem das hier, was zwar nichts mit dem Threading-Problem zu tun hat, aber mir doch bemerkenswert erscheint:

    __fastcall TMonitorDirThread::~TMonitorDirThread()
    {
        ...
        TThread::inherited ();
    }
    

    Deaktiviere mal Laufzeitpackages, nimm dir den Debugger, setze da einen Breakpoint und schau, was da passiert.

    Und um dir gleich mal den Spaß zu verderben: TThread::inherited ist ein Typedef auf TObject; du rufst also den TObject-Konstruktor auf, der ein neues, nicht initialisiertes TThread-Objekt erstellt. Ich weiß nicht, was genau du da vorhattest - vielleicht dachtest du daran, daß man beim Überschreiben virtueller Funktionen den Aufruf gelegentlich an die Basisklasse weiterleiten muß? Das ist zwar richtig, trifft jedoch auf Konstruktoren und Destruktoren in C++ nicht zu; dort passiert das automatisch. (In Delphi hingegen wäre ein "inherited Destroy;" hier durchaus richtig.)

    Eine 10-15s anhaltende Verzögerung konnte ich nicht feststellen. Ich habe allerdings nur dein erstes Beispiel ausführlich getestet. Ist das mit dem zweiten besser reproduzierbar, und kannst du noch etwas ins Detail gehen hinsichtlich der notwendigen Randbedingungen (u.a. C++Builder-Version)?



  • Wie kann man eigentlich auf die Eigenschaft "Terminated" zugreifen?
    Innerhalb von Execute() geht das ohne Probleme

    if(!Terminated)
    {
      // tu irgendwas
    }
    


  • audacia schrieb:

    Zur Situation mit FreeOnTerminate=true kann ich sagen, daß dein Ansatz dann nicht ganz sicher ist:

    __fastcall TForm1::~TForm1(void)
    {
        FMyThread->Terminate(); // -> der Thread kann ab sofort jederzeit terminiert
                                // werden
        FMyThread->WaitFor(); // ... und was, wenn das hier schon passiert ist? Dann
                              // hat sich das Objekt bereits selbst gelöscht - fatal.
        delete FMyThread; // Noch viel schlimmer - wir zerstören es zweimal.
        FMyThread = NULL;
    }
    

    FreeOnTerminate und Terminate()/WaitFor() schließen einander praktisch aus. Mehr noch: wenn ein Thread FreeOnTerminate=true verwendet, darfst du, sobald er läuft, eigentlich nicht mehr von außen auf das Thread-Objekt zugreifen - du weißt nie, ob es überhaupt noch existiert. In diesem Fall ist es notwendig, sich auf andere Kommunikationsmechanismen zu beschränken.

    Du hättest beim Beispiel aus dem ersten Post bleiben sollen. 🙂 Das ist die ursprüngliche Fassung des Threads, der das Verhalben mit diesem Minimalbeispiel zeigt. Und dort ist FreeOnTerminate = false. Bei dem späteren Beispiel hatte ich dann in meienr Verzweiflung alles mögliche getestet, unter anderem auch, was ich so an Hinweisen im Netz gefunden habe... Bis hin zum Inherited.

    audacia schrieb:

    Eine 10-15s anhaltende Verzögerung konnte ich nicht feststellen. Ich habe allerdings nur dein erstes Beispiel ausführlich getestet. Ist das mit dem zweiten besser reproduzierbar, und kannst du noch etwas ins Detail gehen hinsichtlich der notwendigen Randbedingungen (u.a. C++Builder-Version)?

    Das ist im BCB 6 und mit dem Code aus dem ersten Post ist das Problem reproduzierbar. Ich habe das Problem ja auch schlußendlich lösen können, auf Basis des Codes meinem zweiten Post. Allerdings nicht mir VCL-Mitteln, sondern eben mit Events. Keine Wartezeit am Ende, Destruktor der Threads wird aufgerufen (was nach Terminate() auch nicht der Fall ist). Und wie gesagt, die Probleme tauchen nicht im laufenden Betrieb auf, sondern nur, wenn die Anwendung beendet wird und man noch aufräumen möchte, sprich die Threads beenden und freigeben.



  • Joe_M. schrieb:

    Das ist im BCB 6 und mit dem Code aus dem ersten Post ist das Problem reproduzierbar.

    Dann mache ich irgendetwas falsch.

    Kannst du evtl. mal ein vollständiges Projekt hochladen?



  • Na klar. Hier ist ein Minimalprojekt: LINK ENTFERNT

    Sogar ohne, dass da sonst irgendetwas passiert, dauert der Destruktoraufruf von Form1 ca. 7 Sekunden.

    Das alte Projekt habe ich in dieser Form nicht mehr, sondern nur noch die auf Events umgestellte Fassung. Aber die funktioniert ja klaglos und das Beenden und Löschen der Threads bewegt sich im Millisekundenbereich...

    Edit: Link entfernt.



  • Verrückt - ich habe das jetzt mehrere Male durchlaufen lassen, und ich kann es nach wie vor nicht reproduzieren.

    Die einzige wahrnehmbare Verzögerung sind die 200ms des Sleep() in den Hintergrundthreads; das kann man durch Vergrößerung der Verzögerung entsprechend skalieren. Aber das sind maximal 0.8s, in der Praxis meist nur 0.4.

    Was passiert denn während dieser 7s-Verzögerungen, bzw. wo genau treten sie auf (-> ohne Packages und mit Debug-Bibliotheken kompilieren)? Treten sie überhaupt auf, wenn du schrittweise durch den Code gehst?



  • 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?


Anmelden zum Antworten