RepeatingTasks beenden sich nicht richtig
-
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. ?
-
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 woexecute_task
die Task-Funktion synchron aufruft und dann wartet bis es Zeit für den nächsten Aufruf ist.
-
ps:
DieRepeatingTask
Objekte kann man nicht sauber löschen, da join() nicht sicherstellt dassexecute_task()
schon verlassen wurde.
(Und natürlich solltejoin
vermutlich auch im Dtor aufgerufen werden, RRID und so.)
-
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:
DieRepeatingTask
Objekte kann man nicht sauber löschen, da join() nicht sicherstellt dassexecute_task()
schon verlassen wurde.
(Und natürlich solltejoin
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 derexecute_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 einstd::condition_variable_any::wait_for
ersetzen würde. Damit das joinen net unnötig verzögert wird. Dann muss natürlich nemutex
her, dafür brauchtrunning
nimmer atomic sein, weil man ja jetzt diemutex
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.
-
hustbaer schrieb:
Wobei ich dann das
sleep_for
vermutlich durch einstd::condition_variable_any::wait_for
ersetzen würde. Damit das joinen net unnötig verzögert wird. Dann muss natürlich nemutex
her, dafür brauchtrunning
nimmer atomic sein, weil man ja jetzt diemutex
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 bekommtAuf 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 FehlerEr ü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