From f6a6b0b68f9af84f5fc04440d08baf120f7c6136 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 11 Oct 2023 13:21:08 +0200 Subject: [PATCH] Library: allow to bind a member function into self-managed thread Oh my. Yet another hideously complex problem and workaround... Since a week I am like "almost done" --- src/lib/thread.hpp | 70 ++++++- .../thread-wrapper-autonomous-test.cpp | 26 ++- wiki/thinkPad.ichthyo.mm | 177 +++++++++++++++++- 3 files changed, 258 insertions(+), 15 deletions(-) diff --git a/src/lib/thread.hpp b/src/lib/thread.hpp index a356ecf08..ceb52240d 100644 --- a/src/lib/thread.hpp +++ b/src/lib/thread.hpp @@ -360,6 +360,27 @@ namespace lib { } }; + template + struct InstancePlaceholder { }; + + template + inline INVO + lateBindInstance (W&, INVO invocation) + { + return invocation; + } + + template + inline auto + lateBindInstance (W& instance, const tuple, ARGS...> invocation) + { + auto splice = [&instance](auto f, auto&, auto ...args) + { + TAR* instancePtr = static_cast (&instance); + return tuple{move(f), instancePtr, move(args)...}; + }; + return std::apply (splice, invocation); + } /** * Policy-based configuration of thread lifecycle @@ -374,6 +395,7 @@ namespace lib { void invokeThreadFunction (ARGS&& ...args) { + if (not Policy::isLive()) return; Policy::handle_begin_thread(); Policy::markThreadStart(); Policy::perform_thread_function (forward (args)...); @@ -395,6 +417,19 @@ namespace lib { { } public: + /** + * Build the invocation tuple, using #invokeThreadFunction + * to delegate to the user-provided functor and arguments + */ + template + static auto + buildInvocation (W& wrapper, tuple&& invocation) + { //the thread-main function + return tuple_cat (tuple{&ThreadLifecycle::invokeThreadFunction + , &wrapper} // passing the wrapper as instance-this + ,move (invocation)); //...invokeThreadFunction() in turn delegates + } // to the user-provided thread-operation + /** * Build a λ actually to launch the given thread operation later, * after the thread-wrapper-object is fully initialised. @@ -413,13 +448,12 @@ namespace lib { buildLauncher (INVO&& ...args) { tuple...> argCopy{forward (args)...}; - return [invocation = move(argCopy)] //Note: functor+args bound by-value into the λ + return [invocation = move(argCopy)]// Note: functor+args bound by-value into the λ (ThreadLifecycle& wrapper) - { //the thread-main function - wrapper.launchThread (tuple_cat (tuple{&ThreadLifecycle::invokeThreadFunction...> - , &wrapper} // passing the wrapper as instance-this - ,move (invocation))); //...invokeThreadFunction() in turn delegates - }; // to the user-provided thread-operation + { // special treatment for launchDetached + auto boundInvocation = lateBindInstance (wrapper, move (invocation)); + wrapper.launchThread (buildInvocation (wrapper, move(boundInvocation))); + }; } @@ -743,6 +777,30 @@ namespace lib { .threadID (threadID)); } + /** Special variant bind a member function of the subclass into the autonomous thread */ + template + inline void + launchDetached (string const& threadID, void (TAR::*memFun) (ARGS...), ARGS ...args) + { + using Launch = typename TAR::Launch; + launchDetached (Launch{std::move (memFun) + ,thread::InstancePlaceholder{} + ,forward (args)... + } + .threadID (threadID)); + } + + /** Special variant without explicitly given thread-ID */ + template + inline void + launchDetached (void (TAR::*memFun) (ARGS...), ARGS ...args) + { + return launchDetached (util::joinDash (lib::meta::typeSymbol(), args...) + ,memFun + ,forward (args)... + ); + } + } // namespace lib #endif /*LIB_THREAD_H*/ diff --git a/tests/library/thread-wrapper-autonomous-test.cpp b/tests/library/thread-wrapper-autonomous-test.cpp index bb6e4e5e7..090adffd1 100644 --- a/tests/library/thread-wrapper-autonomous-test.cpp +++ b/tests/library/thread-wrapper-autonomous-test.cpp @@ -26,7 +26,9 @@ #include "lib/test/run.hpp" +#include "lib/test/testdummy.hpp" #include "lib/thread.hpp" +#include "lib/test/diagnostic-output.hpp"///////////////////TODO #include #include @@ -83,22 +85,30 @@ namespace test{ void verifyMemoryManagement() { - UNIMPLEMENTED ("verify thread manages itself"); struct TestThread - : Thread + : ThreadHookable { - using Thread::Thread; + using ThreadHookable::ThreadHookable; - uint local{0}; + Dummy watcher; void - doIt (uint a, uint b) ///< the actual operation running in a separate thread + doIt (int extra) { - uint sum = a + b; -// sleep_for (microseconds{sum}); // Note: explicit random delay before local store - local = sum; + watcher.setVal (extra); +SHOW_EXPR(extra) + sleep_for (5ms); +SHOW_EXPR("bye") } }; + // + CHECK (0 == Dummy::checksum()); + // + launchDetached (&TestThread::doIt, 55); + + CHECK (0 < Dummy::checksum()); + sleep_for (10ms); + CHECK (0 == Dummy::checksum()); } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 44534c55b..0e371ae9f 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -65342,9 +65342,122 @@ - + + + + + + + + + + + + +

+ denn: grade hier für den »Autonomous Thread« kommt man von außen nicht an die Addresse des Objektes ran +

+
    +
  • + die Addresse ist vor der Allokation nicht bekannt, kann daher nicht in ein Lambda gebunden werden +
  • +
  • + eine statische Funktion würde zusätzlich noch einen Singleton-Mechanismus brauchen +
  • +
  • + ohne Zugang zum Objekt ist es aber auch sinnlos, zusätzliche Daten und Funktionen im abgeleiteten Objekt zu haben +
  • +
+ + +
+ +
+ + + + + + + + + + + + +

+ Wenn wir es irgendwie schaffen könnten, das Objekt schon auf den Heap zu allozieren, aber zu verhindern, daß es „fliegt“ (bzw. wirklich konstruiert wird). Dann wüßten wir die Addresse. Danach würden wir ein placement-New in die Allokation machen, und könnten diesem tatsächlichen Konstruktor-Aufruf ganz verträumt den richtigen Instanz-Pointer mitgeben. Vorraussetzung wäre allerdings, daß sich diese manipulierte Allokation später ganz gewöhnlich per operator delete wieder entfernen ließe. Das wäre dann entweder ein fragiler Hack, der sich auf Plattform-Interna abstützt, oder es wäre ein custom-operator-new im Stil der 90er-Jahre +

+ + +
+ +
+ +<html> + <head> + + + </head> + <body> + <p> + Daf&#252;r br&#228;uchte ich eine Stelle, an der die tats&#228;chliche Instanz bereits + bekannt ist, aber die Invocation nochin generischer Form vorliegt. In + einem solchen Kontext k&#246;nnte man dann einen Marker-Typ erkennen und an + die tats&#228;chliche Instanz binden. + </p> + <p> + + </p> + <p> + <u>&#55357;&#56481; Und in der Tat</u>: diesen Kontext g&#228;be es in der + Hilfsfunktion buildLauncher() + </p> + </body> +</html> + + + + + + + + + +
    +
  • + zuverlässig: sie wird zur Compile-Zeit eingebunden und ist über das Typsystem abgesichert +
  • +
  • + sicher: sie baut auf einen speziellen Marker-Typ auf — wenn er fehlt, passiert nichts an der Stelle +
  • +
  • + effizient: da sie in einer Funktionstemplate-Instantiierung steht, wird sie nur im fraglichen Spezialfall getriggert +
  • +
+ + +
+
+ + +
+ + + + + + + + + + + + + +
@@ -65762,6 +65875,67 @@ + + + + + + +

+ allerdings: abgeleitete Klassen leben gefährlich +

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

+ Ein normaler Thread (und ein Joinable) würden den Aufruf des Destruktors verbieten, und damit die Applikation terminieren +

+ + +
+
+ + + + + + +

+ aber der Thread ist schon „unterwegs“ +

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

+ ...aber das gibt mein Framework nicht her +

+ + +
+
+
+
@@ -66134,6 +66308,7 @@ +