RepeatingTasks beenden sich nicht richtig


  • Mod

    Tuxx schrieb:

    Deswegen ist das detach toll, damit es unabhängig im Hintergrund läuft.

    Das war ja auch der Plan 😃

    Die grundlegende Frage ist: Was soll passieren? Soll das Programm tendenziell eher warten, bis alle Subprozesse fertig sind, bevor es sich beendet? Oder sollen möglichst schnell alle Subprozesse beendet werden (auch wenn sie noch nicht fertig sind), wenn das Programm sich beenden möchte? Oder etwas dazwischen? Wenn diese Frage geklärt ist, kann man über die genaue Umsetzung reden.



  • Hallo SeppJ,

    da gebe ich dir vollkommen Recht 😃

    Ich habe mein Programm am laufen.
    Dieses erzeugt einen Server, der darauf wartet, dass sich ein Client verbindet.
    Ist dies geschehen, läuft mein eigentliches Programm, in dem ich zwei RepeatingTasks benötige.

    Wird der Client unverhofft gekappt oder das System will sich herunterfahren,
    muss das eigentliche Programm mit allen offenen Threads geschlossen werden, der Client eventuell geschlossen werden, der Server heruntergefahren werden und dann beendet sich auch meine main().

    Bis jetzt bekomme ich immer Fehler, dass auf Speicher noch zugegriffen wird, der nicht mehr existiert.
    Also muss ich irgendwie sicherstellen, dass das programm in dem Falle wartet, bis alle offenen Threads geschlossen wurden, bevor der Client und Server geschlossen wird.


  • Mod

    Wenn wir wirklich brav warten dürfen, bis alles fertig ist, können wir einfach mitzählen:

    #include <thread>
    #include <atomic>
    #include <chrono>
    #include <functional>
    
    class RepeatingTask
    {
    private:
      std::atomic_int running;
      std::function<void()> task;
      std::chrono::nanoseconds interval;
      std::atomic_uint num_running_threads;
    
      void execute_task()
      {
        while(running)
          {
            std::thread t([this]()
                          {
                            ++num_running_threads;
                            task();
                            --num_running_threads;
                          }
                          );
            t.detach();
            std::this_thread::sleep_for(interval);
          }
      }
    
    public:
      RepeatingTask(std::function<void()> task, std::chrono::nanoseconds interval):
        running(0), task(task), interval(interval), num_running_threads(0) {}
    
      RepeatingTask(const RepeatingTask&) = delete;
      RepeatingTask &operator=(const RepeatingTask&) = delete;
    
      void run()
      {
        running = 1;
        std::thread t(std::bind(&RepeatingTask::execute_task, this));
        t.detach();
      }
    
      void stop() {running = 0;}
      void join()
      {
        stop();
        while(num_running_threads)
          {
            std::this_thread::yield();
          }
      } 
    };
    
    // Beispiel
    #include <iostream>
    void ausgabe()
    {
      using namespace std::chrono_literals;
      for (int i = 1; i < 5; ++i)
        {
          std::cout << "Thread Nummer " << std::this_thread::get_id() << ", bei Zahl " << i << '\n';
          std::this_thread::sleep_for(1.4s);
        }
    }
    
    int main()
    {
      using namespace std::chrono_literals;
      RepeatingTask task(ausgabe, 1s);
      task.run();
      std::this_thread::sleep_for(5s);
      std::cout << "Leite Programmbeendigung ein.\n";
      task.join();
      std::cout << "Alle Hintergrundanwendungen sind fertig. Beende.\n";
    }
    


  • Danke, genau das, was ich gesucht habe 👍 👍 👍



  • Eine Frage noch an dich, SeppJ:

    Den Code über RepeatingTask hast du ja geschrieben oder ?

    Muss ich das in meinem Code irgendwie kennzeichnen zwecks copyright etc. ?


  • Mod

    Tuxx schrieb:

    Eine Frage noch an dich, SeppJ:

    Den Code über RepeatingTask hast du ja geschrieben oder ?

    Muss ich das in meinem Code irgendwie kennzeichnen zwecks copyright etc. ?

    Ja, das ist von mir. Aber die paar Zeilen, die ich da mal eben schnell runtergehackt habe, kannst du gerne übernehmen, das ist nicht schützenswert.



  • Wieso wird da eigentlich bei jedem Tick ein neuere Thread erzeugt, auch wenn die Funktion vom alten Tick noch nicht fertig gelaufen ist?
    Ist das wirklich Absicht?

    Ich hätte - wenn nicht speziell angegeben ist dass man es anders braucht - grundsätzlich mal die Version mit 1 Thread pro RepeatingTask implementiert. Also die wo execute_task die Task-Funktion synchron aufruft und dann wartet bis es Zeit für den nächsten Aufruf ist.



  • ps:
    Die RepeatingTask Objekte kann man nicht sauber löschen, da join() nicht sicherstellt dass execute_task() schon verlassen wurde.
    (Und natürlich sollte join vermutlich auch im Dtor aufgerufen werden, RRID und so.)


  • Mod

    hustbaer schrieb:

    Wieso wird da eigentlich bei jedem Tick ein neuere Thread erzeugt, auch wenn die Funktion vom alten Tick noch nicht fertig gelaufen ist?
    Ist das wirklich Absicht?

    Das war es, wie ich die Anforderungen des Threaderstellers in seinem Ursprungsthread verstanden habe. Ob der Threadersteller seine Anforderung auch wirklich richtig herüber gebracht hat, habe ich mich auch gefragt und daher absichtlich Beispiele konstruiert, die diese Eigenschaft demonstrieren. Er hat sich nicht beschwert.

    Falls man hingegen jeweils eine neue Ausführung der Aufgabe in einem bestimmten Abstand zur Beendigung der letzten Aufgabe haben möchte, wird das ganze Problem (sowohl das aktuelle, als auch die Ursprungsfrage) ziemlich trivial.

    hustbaer schrieb:

    ps:
    Die RepeatingTask Objekte kann man nicht sauber löschen, da join() nicht sicherstellt dass execute_task() schon verlassen wurde.
    (Und natürlich sollte join vermutlich auch im Dtor aufgerufen werden, RRID und so.)

    Guter Hinweis, habe ich übersehen. Hmm, trickreich. Da muss ich mal etwas darüber nachdenken. edit: Sollte gehen, indem man sich einen Handle auf den execute_task-Thread merkt und diesen in RepeatingTask::join selber joined. Oder übersehe ich da etwas?



  • Jo, ich frage auch hauptsächlich um dem Threadersteller das nochmal klarzumachen. Weil ich das im ursprünglichen Thread nicht so verstanden habe. Und wenn man es nicht so braucht, dann will man es vermutlich auch nicht so 🙂

    SeppJ schrieb:

    Falls man hingegen jeweils eine neue Ausführung der Aufgabe in einem bestimmten Abstand zur Beendigung der letzten Aufgabe haben möchte, wird das ganze Problem (sowohl das aktuelle, als auch die Ursprungsfrage) ziemlich trivial.

    Fixes Delay zwischen Ende des letzten Aufrufs und Start des neuen ist meist auch doof. Meist will man fixe Abstände zwischen den Anfängen der Aufrufe, ausser eben wenn der letzte Aufruf zu lange gedauert hat, dann den nächsten Aufruf einfach ohne Verzögerung. Wie bei Spielen mit VSync quasi 🙂

    Ist aber natürlich auch immer noch halbwegs einfach zu machen.

    hustbaer schrieb:

    Hmm, trickreich. Da muss ich mal etwas darüber nachdenken.

    Müsste ein join() auf den Thread der execute_task() ausführt reichen, oder?



  • SeppJ schrieb:

    edit: Sollte gehen, indem man sich einen Handle auf den execute_task-Thread merkt und diesen in RepeatingTask::join selber joined. Oder übersehe ich da etwas?

    Nö genau so hätte ich das auch gemacht.

    Wobei ich dann das sleep_for vermutlich durch ein std::condition_variable_any::wait_for ersetzen würde. Damit das joinen net unnötig verzögert wird. Dann muss natürlich ne mutex her, dafür braucht running nimmer atomic sein, weil man ja jetzt die mutex dafür hernehmen kann.

    Und wenn man dann eh schon ne CV + mutex hat, dann kann man auch gleich noch ne 2. CV für num_running_threads machen und den spin-wait durch nen CV-wait ersetzen.


  • Mod

    hustbaer schrieb:

    Wobei ich dann das sleep_for vermutlich durch ein std::condition_variable_any::wait_for ersetzen würde. Damit das joinen net unnötig verzögert wird. Dann muss natürlich ne mutex her, dafür braucht running nimmer atomic sein, weil man ja jetzt die mutex dafür hernehmen kann.

    Jupp, genau mein Gedanke.

    Und wenn man dann eh schon ne CV + mutex hat, dann kann man auch gleich noch ne 2. CV für num_running_threads machen und den spin-wait durch nen CV-wait ersetzen.

    Jetzt ist bloß noch die Frage, ob der TE mit der Beschreibung alleine zurecht kommt 🙂



  • Nö, aber dafür hat er ja den SeppJ 😃 🤡



  • Hallo Leute,

    erstmal danke SeppJ, dass ich den Code einfach so verwenden darf,
    mein Chef fragte nämlich, nicht dass unsere Firma da Probleme bekommt 🙂

    Auf die Idee mit mutex und CV bin ich auch schon gekommen gestern,
    nachdem mein Programm immer abstürzte wegen false memory access und abnormal interupt.

    Ich hab eigentlich genau das, was ihr in der letzten Nacht ;-), wo ich friedlich geschlafen habe, erörtert habt, gestern abend vor dem Feierabend noch umgesetzt.
    Ich hab nen mutex und eine CV und ein extra exec thread, der execute_task aufruft und einfach nur wartet und sicherstellt, dass alle offenen threads zum schluss geweckt und ordentlich beendet werden und erst dann beendet sich exec und mein Programm endet ohne Fehler 🙂

    Er überprüft einfach in der stop(), ob exec joinable ist und wenn ja, macht er den join() und wartet...

    Und wenn ich nicht gestern darauf gekommen wäre, hätte ich es jetzt gewusst durch eure gute Beschreibung - also Ja, ich wäre mit eurer Beschreibung zurecht gekommen

    😃 😃 😃


Anmelden zum Antworten