From 5f9683ef108772aa3fdee7b82cd36ef73b2b48b5 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 10 Oct 2023 02:55:23 +0200 Subject: [PATCH] Library: policy for self-managed thread MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ...after resolving the fundamental design problems, a policy mix-in can be defined now for a thread that deletes its own wrapper at the end of the thread-function. Such a setup would allow for »fire-and-forget« threads, but with wrapper and ensuring safe allocations. The prominent use case for such a setup would be the GUI-Thread. --- src/lib/thread.cpp | 8 +- src/lib/thread.hpp | 85 ++++++++++-- src/steam/control/steam-dispatcher.cpp | 2 +- .../thread-wrapper-autonomous-test.cpp | 43 +----- wiki/thinkPad.ichthyo.mm | 122 ++++++++++++++++-- 5 files changed, 197 insertions(+), 63 deletions(-) diff --git a/src/lib/thread.cpp b/src/lib/thread.cpp index 8d1a1bf9c..8c18db741 100644 --- a/src/lib/thread.cpp +++ b/src/lib/thread.cpp @@ -67,17 +67,17 @@ namespace thread{ void - ThreadWrapper::markThreadStart (string id) + ThreadWrapper::markThreadStart() { - TRACE (thread, "%s", lifecycleMsg ("start...", id).c_str()); + TRACE (thread, "%s", lifecycleMsg ("start...", threadID_).c_str()); setThreadName(); } void - ThreadWrapper::markThreadEnd(string id) + ThreadWrapper::markThreadEnd() { - TRACE (thread, "%s", lifecycleMsg ("finished.", id).c_str()); + TRACE (thread, "%s", lifecycleMsg ("finished.", threadID_).c_str()); } diff --git a/src/lib/thread.hpp b/src/lib/thread.hpp index ff717d7f9..7b81d7413 100644 --- a/src/lib/thread.hpp +++ b/src/lib/thread.hpp @@ -123,6 +123,7 @@ #include "lib/error.hpp" #include "lib/nocopy.hpp" #include "include/logging.h" +#include "lib/meta/trait.hpp" #include "lib/format-util.hpp" #include "lib/result.hpp" @@ -207,10 +208,15 @@ namespace lib { /** detect if the currently executing code runs within this thread */ bool invokedWithinThread() const; - void markThreadStart(string); - void markThreadEnd (string); + void markThreadStart(); + void markThreadEnd (); void setThreadName (); void waitGracePeriod() noexcept; + + /* empty implementation for some policy methods */ + void handle_begin_thread() { } + void handle_after_thread() { } + void handle_thread_still_running() { } }; @@ -222,7 +228,7 @@ namespace lib { * - thread detaches before terminating * - »grace period« for thread to terminate on shutdown */ - template + template struct PolicyLaunchOnly : BAS { @@ -240,7 +246,7 @@ namespace lib { } void - handle_end_of_thread() + handle_after_thread() { if (BAS::isLive()) BAS::threadImpl_.detach(); @@ -254,6 +260,36 @@ namespace lib { }; + /** + * Thread Lifecycle Policy Extension: + * additionally self-manage the thread-wrapper allocation. + * @warning the thread-wrapper must have been heap-allocated. + */ + template + struct PolicySelfManaged + : PolicyLaunchOnly + { + using BasePol = PolicyLaunchOnly; + using BasePol::BasePol; + + void + handle_after_thread() + { + TAR* selfAllocation = static_cast( + static_cast (this)); + if (BAS::isLive()) + BAS::threadImpl_.detach(); + delete selfAllocation; + } + + void + handle_thread_still_running() + { + ALERT (thread, "Self-managed thread was deleted from outside. Abort."); + } + }; + + /** * Thread Lifecycle Policy: * - thread with the ability to publish results @@ -286,7 +322,7 @@ namespace lib { } void - handle_end_of_thread() + handle_after_thread() { /* do nothing -- thread must be joined manually */; } @@ -312,11 +348,11 @@ namespace lib { void invokeThreadFunction (ARGS&& ...args) { - string id{Policy::threadID_}; // local copy - Policy::markThreadStart(id); + Policy::handle_begin_thread(); + Policy::markThreadStart(); Policy::perform_thread_function (forward (args)...); - Policy::markThreadEnd(id); - Policy::handle_end_of_thread(); + Policy::markThreadEnd(); + Policy::handle_after_thread(); } @@ -490,7 +526,7 @@ namespace lib { * @deprecated can't sleep well while this function is exposed; * need a prime solution to address this relevant use case ////////////////////////////////////////OOO allow for a thread with explicit lifecycle */ - void detach() { ThreadLifecycle::handle_end_of_thread(); } + void detach() { ThreadLifecycle::handle_after_thread(); } }; @@ -541,5 +577,34 @@ namespace lib { + /************************************************************************//** + * Special configuration for a »fire-and-forget«-Thread. + * @internal this class is meant for subclassing. Start with #launchDetached() + * @tparam TAR the concrete type of the subclass to be started as autonomous, + * self-managed thread. Must be passed down since thread deletes itself. + */ + class ThreadAutonomous + : public thread::ThreadLifecycle + { + using Impl = thread::ThreadLifecycle; + public: + using Impl::Impl; + }; + + /** + * Launch an autonomous self-managing thread (and forget about it). + * The thread-wrapper is allocated to the heap and will delete itself on termination. + * @tparam TAR concrete type of the subclass to be started as autonomous detached thread. + * @param args a valid argument list to call the ctor of thread::ThreadLifecycle + */ + template + inline void + launchDetached (INVO&& ...args) + { + new ThreadAutonomous{forward (args)...}; // Thread will pick up and manage *this + } + + + } // namespace lib #endif /*LIB_THREAD_H*/ diff --git a/src/steam/control/steam-dispatcher.cpp b/src/steam/control/steam-dispatcher.cpp index 5b7b321eb..ca7d9434e 100644 --- a/src/steam/control/steam-dispatcher.cpp +++ b/src/steam/control/steam-dispatcher.cpp @@ -144,7 +144,7 @@ namespace control { { return not queue_.empty(); }) - , thread_{"Lumiera Session" + , thread_{"Session" ,&DispatcherLoop::runSessionThread , this, notification} { diff --git a/tests/library/thread-wrapper-autonomous-test.cpp b/tests/library/thread-wrapper-autonomous-test.cpp index 5cf5f34cf..bb6e4e5e7 100644 --- a/tests/library/thread-wrapper-autonomous-test.cpp +++ b/tests/library/thread-wrapper-autonomous-test.cpp @@ -27,19 +27,15 @@ #include "lib/test/run.hpp" #include "lib/thread.hpp" -#include "lib/iter-explorer.hpp" -#include "lib/scoped-collection.hpp" -#include "lib/test/microbenchmark.hpp" #include #include using test::Test; -using lib::explore; using std::atomic_uint; using std::this_thread::yield; using std::this_thread::sleep_for; -using std::chrono::microseconds; +using namespace std::chrono_literals; namespace lib { @@ -74,11 +70,10 @@ namespace test{ demonstrateSimpleUsage() { atomic_uint i{0}; - Thread thread("counter", [&]{ ++i; }); // bind a λ and launch thread - while (thread) yield(); // ensure thread has finished and detached + launchDetached ("anarchy", [&]{ ++i; }); + sleep_for(1ms); CHECK (i == 1); // verify the effect has taken place - UNIMPLEMENTED ("actually launch detached"); } @@ -88,6 +83,7 @@ namespace test{ void verifyMemoryManagement() { + UNIMPLEMENTED ("verify thread manages itself"); struct TestThread : Thread { @@ -99,39 +95,10 @@ namespace test{ doIt (uint a, uint b) ///< the actual operation running in a separate thread { uint sum = a + b; - sleep_for (microseconds{sum}); // Note: explicit random delay before local store +// sleep_for (microseconds{sum}); // Note: explicit random delay before local store local = sum; } }; - - // prepare Storage for these objects (not created yet) - lib::ScopedCollection threads{NUM_THREADS}; - - size_t checkSum = 0; - size_t globalSum = 0; - auto launchThreads = [&] - { - for (uint i=1; i<=NUM_THREADS; ++i) - { - uint x = rand() % 1000; - globalSum += (i + x); - threads.emplace (&TestThread::doIt, i, x); - } // Note: bind to member function, copying arguments - - while (explore(threads).has_any()) - yield(); // wait for all threads to have detached - - for (auto& t : threads) - { - CHECK (0 < t.local); - checkSum += t.local; - } - }; - - double runTime = benchmarkTime (launchThreads, REPETITIONS); - - CHECK (checkSum == globalSum); // sum of precomputed random numbers matches sum from threads - CHECK (runTime < NUM_THREADS * 1000/2); // random sleep time should be > 500ms on average } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 19a2e7946..4b7625c9c 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -65196,6 +65196,13 @@

+ + + + + + + @@ -65234,10 +65241,76 @@ - + + + + + + + + + + + + + +

+ und zwar, sofern oben in der konkreten Subklasse irgendwo ein mix-in verwendet wird — in dem Fall müßte nämlich der Pointer nachjustiert werden +

+ +
+ +
+
+
+ + + + + + + + +

+ ohne Subclassing ist die ganze Übung etwas überzogen +

+ +
+ + + + + +

+ ...es ist ja nett, daß der Thread-Wrapper sich selbst verwaltet — und der ganze Aufwand bloß für zwei interne Datenfelder?  wäre da nicht ein »workaround« angemessener? +

+ +
+
+ + + + + + +

+ wozu dann überhaupt noch das Front-End? Um einen etwas sonderbaren new-Aufruf einzupacken? +

+ +
+
+ + + + + + + + + @@ -65571,7 +65644,7 @@ - + @@ -65586,11 +65659,11 @@ das Kopieren des Funktors und der Argumente ebenfalls ohne Probleme erfolgt sein
  • - aber danach ein Problem im bereits eingerichteten Thread auftreten, bevor unser Code den Manager (smart-ptr) aktivieren kann. + aber danach ein Problem im bereits eingerichteten Thread auftreten, bevor unser in den try-catch-Block eintritt.
  • - Hierzu kommt nur wenig in Frage: einmal die Invocation des Funktors selber mit als Wert vorliegenden Parametern, sowie dann unser eigener Library-Code bis zu der Stelle, an der der Pointer gesichert ist. Da sehe ich im Moment wenig Raum für Fehler. Funktoren sind ja entweder Pointer oder Objekte, und durch die erzwungene Kopie sind die tatsächlich dann aktiven Instanzen bereits konstruiert. Ein std::function verwendet zwar einen Invoker, aber das ist ein Trampolin, um die verschiedenen Aufruf-Technologien zu nivellieren; mir ist nicht bekannt, daß das für den Aufruf noch irgend etwas macht, was scheitern könnte. Bleiben also nur noch spezielle esoterische Argument-Typen. Und außerdem müßte der Optimiser so dämlich sein, ein bereits kopiertes Argument noch einmal zu kopieren nur für den Aufruf. Abgesehen davon könnten diese »esoterischen« Typen nur bei den weiteren Funktions-Argumenten auftreten (nicht der Funktor, nicht der this-Pointer). Danach haben wir nur noch einen nicht-virtuellen Aufruf in die Policy (ist inline) und das speichern des this-Pointers in den smart-ptr. Wenn danach noch was passiert, terminiert der Thread, der Funktor wird de-alloziert, und der darin eingebettete smart-ptr waltet seines Amtes + Hierzu kommt nur wenig in Frage: einmal die Invocation des Funktors selber mit als Wert vorliegenden Parametern, sowie dann unser eigener Library-Code bis zu der Stelle, an der der control-flow in den try-catch eintritt, sowie das Logging danach. Da sehe ich im Moment wenig Raum für Fehler. Funktoren sind ja entweder Pointer oder Objekte, und durch die erzwungene Kopie sind die tatsächlich dann aktiven Instanzen bereits konstruiert. Ein std::function verwendet zwar einen Invoker, aber das ist ein Trampolin, um die verschiedenen Aufruf-Technologien zu nivellieren; mir ist nicht bekannt, daß das für den Aufruf noch irgend etwas macht, was scheitern könnte. Bleiben also nur noch spezielle esoterische Argument-Typen. Und außerdem müßte der Optimiser so dämlich sein, ein bereits kopiertes Argument noch einmal zu kopieren nur für den Aufruf. Abgesehen davon könnten diese »esoterischen« Typen nur bei den weiteren Funktions-Argumenten auftreten (nicht der Funktor, nicht der this-Pointer). Danach haben wir nur noch nicht-virtuelle Aufrufe in die Policy (ist inline) und das Logging — dieses  stellt vermutlich die größte Gefahr dar; da hier aber kein schützender try-catch mehr darüber liegt, führen Fehler hier sofort zu std::terminate.

    @@ -65769,8 +65842,7 @@                            .atEnd(terminationHook)});

    - - +
    @@ -65829,8 +65901,7 @@ wenn halt Argument-Packs einfach als Typ repräsentierbar wären — aber dem ist nicht so; man muß gegen ein getemplatetes Argument matchen, um aus einem Argument-Pack einen anderen Argument-Pack zu konstruieren

    - - +
    @@ -65896,7 +65967,17 @@ - + + + + + + + + + + + @@ -65955,6 +66036,18 @@ + + + + + + + + + + + + @@ -82097,10 +82190,19 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
    - + + + + + + + + + +