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"
This commit is contained in:
parent
42eba8425a
commit
f6a6b0b68f
3 changed files with 258 additions and 15 deletions
|
|
@ -360,6 +360,27 @@ namespace lib {
|
|||
}
|
||||
};
|
||||
|
||||
template<class TAR>
|
||||
struct InstancePlaceholder { };
|
||||
|
||||
template<class W, class INVO>
|
||||
inline INVO
|
||||
lateBindInstance (W&, INVO invocation)
|
||||
{
|
||||
return invocation;
|
||||
}
|
||||
|
||||
template<class W, class F, class TAR, typename...ARGS>
|
||||
inline auto
|
||||
lateBindInstance (W& instance, const tuple<F, InstancePlaceholder<TAR>, ARGS...> invocation)
|
||||
{
|
||||
auto splice = [&instance](auto f, auto&, auto ...args)
|
||||
{
|
||||
TAR* instancePtr = static_cast<TAR*> (&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> (args)...);
|
||||
|
|
@ -395,6 +417,19 @@ namespace lib {
|
|||
{ }
|
||||
|
||||
public:
|
||||
/**
|
||||
* Build the invocation tuple, using #invokeThreadFunction
|
||||
* to delegate to the user-provided functor and arguments
|
||||
*/
|
||||
template<class W, class...INVO>
|
||||
static auto
|
||||
buildInvocation (W& wrapper, tuple<INVO...>&& invocation)
|
||||
{ //the thread-main function
|
||||
return tuple_cat (tuple{&ThreadLifecycle::invokeThreadFunction<INVO...>
|
||||
, &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<decay_t<INVO>...> argCopy{forward<INVO> (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<decay_t<INVO>...>
|
||||
, &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<class TAR, typename...ARGS>
|
||||
inline void
|
||||
launchDetached (string const& threadID, void (TAR::*memFun) (ARGS...), ARGS ...args)
|
||||
{
|
||||
using Launch = typename TAR::Launch;
|
||||
launchDetached<TAR> (Launch{std::move (memFun)
|
||||
,thread::InstancePlaceholder<TAR>{}
|
||||
,forward<ARGS> (args)...
|
||||
}
|
||||
.threadID (threadID));
|
||||
}
|
||||
|
||||
/** Special variant without explicitly given thread-ID */
|
||||
template<class TAR, typename...ARGS>
|
||||
inline void
|
||||
launchDetached (void (TAR::*memFun) (ARGS...), ARGS ...args)
|
||||
{
|
||||
return launchDetached (util::joinDash (lib::meta::typeSymbol<TAR>(), args...)
|
||||
,memFun
|
||||
,forward<ARGS> (args)...
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
} // namespace lib
|
||||
#endif /*LIB_THREAD_H*/
|
||||
|
|
|
|||
|
|
@ -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 <atomic>
|
||||
#include <chrono>
|
||||
|
|
@ -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> (&TestThread::doIt, 55);
|
||||
|
||||
CHECK (0 < Dummy::checksum());
|
||||
sleep_for (10ms);
|
||||
CHECK (0 == Dummy::checksum());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -65342,9 +65342,122 @@
|
|||
<node CREATED="1696965933149" ID="ID_1573026703" MODIFIED="1696965943299" TEXT="damit sind alle Probleme auf einmal erschlagen!"/>
|
||||
<node CREATED="1696965943955" ID="ID_1421528010" MODIFIED="1696965960462" TEXT="der Code der Callbacks steht direkt neben dem »freien« Heap-new"/>
|
||||
<node CREATED="1696965962769" ID="ID_250643272" MODIFIED="1696965971748" TEXT="die down-cast ist nun sicher"/>
|
||||
<node CREATED="1696965987578" ID="ID_1670280316" MODIFIED="1696965998650" TEXT="man kann beliebige Subtypen verwenden"/>
|
||||
<node COLOR="#5b280f" CREATED="1696965987578" ID="ID_1670280316" MODIFIED="1696976650429" TEXT="man kann beliebige Subtypen verwenden">
|
||||
<arrowlink COLOR="#f73362" DESTINATION="ID_1651696265" ENDARROW="Default" ENDINCLINATION="-61;-30;" ID="Arrow_ID_120849360" STARTARROW="None" STARTINCLINATION="283;19;"/>
|
||||
<icon BUILTIN="closed"/>
|
||||
</node>
|
||||
<node CREATED="1696965973496" ID="ID_1562329556" MODIFIED="1696965986393" TEXT="und eine sonderbare spezial-Policy fällt komplett weg"/>
|
||||
<node CREATED="1696965999636" ID="ID_1837122164" MODIFIED="1696966015867" TEXT="es gibt keine spezielle Thread-Klasse mehr, die man »nicht verwenden« soll"/>
|
||||
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1696976539898" ID="ID_57182560" MODIFIED="1696976562311" TEXT="ABER: kann mit diesem Design keine Member-Funktionen in den Thread binden">
|
||||
<icon BUILTIN="broken-line"/>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696976568054" ID="ID_1651696265" MODIFIED="1696976852099" TEXT="das macht abgeleitete Klassen effektiv sinnlos">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
denn: grade hier für den »Autonomous Thread« kommt man von außen nicht an die Addresse des Objektes ran
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
die Addresse ist vor der Allokation nicht bekannt, kann daher nicht in ein Lambda gebunden werden
|
||||
</li>
|
||||
<li>
|
||||
eine statische Funktion würde zusätzlich noch einen Singleton-Mechanismus brauchen
|
||||
</li>
|
||||
<li>
|
||||
ohne Zugang zum Objekt ist es aber auch sinnlos, zusätzliche Daten und Funktionen im abgeleiteten Objekt zu haben
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<linktarget COLOR="#f73362" DESTINATION="ID_1651696265" ENDARROW="Default" ENDINCLINATION="-61;-30;" ID="Arrow_ID_120849360" SOURCE="ID_1670280316" STARTARROW="None" STARTINCLINATION="283;19;"/>
|
||||
</node>
|
||||
<node CREATED="1696976853336" ID="ID_372976819" MODIFIED="1696976877733" TEXT="das führt die Idee mit einer selbst-verwalteten Allokation ad absurdum">
|
||||
<icon BUILTIN="smiley-angry"/>
|
||||
</node>
|
||||
<node CREATED="1696976940356" ID="ID_232121625" MODIFIED="1696977061323" TEXT="hier rächt sich auch das »Zwiebelschalen-Design«: der Launch-builder ist opaque"/>
|
||||
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1696978145159" ID="ID_314579443" MODIFIED="1696978153755" TEXT="gibt es überhaupt einen Fix?">
|
||||
<icon BUILTIN="help"/>
|
||||
<node COLOR="#5b280f" CREATED="1696978155858" ID="ID_229604383" MODIFIED="1696978809501" TEXT="übler Hack: Fake-Allokation + placement-New">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
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 <i>tatsächlichen </i>Konstruktor-Aufruf <i>ganz verträumt </i>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
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<icon BUILTIN="closed"/>
|
||||
</node>
|
||||
<node CREATED="1696978523249" ID="ID_1356286451" MODIFIED="1697023022905" TEXT="einen Platzhalter spät an die eigentliche Instanz binden">
|
||||
<richcontent TYPE="NOTE"><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>
|
||||
</richcontent>
|
||||
<arrowlink COLOR="#3551b4" DESTINATION="ID_1017963709" ENDARROW="Default" ENDINCLINATION="8;-31;" ID="Arrow_ID_855336150" STARTARROW="None" STARTINCLINATION="-132;6;"/>
|
||||
<icon BUILTIN="forward"/>
|
||||
<node CREATED="1696978822594" ID="ID_1410342065" MODIFIED="1696979058894" TEXT="diese Lösung wäre zuverlässig, sicher und effizient">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<ul>
|
||||
<li>
|
||||
zuverlässig: sie wird zur Compile-Zeit eingebunden und ist über das Typsystem abgesichert
|
||||
</li>
|
||||
<li>
|
||||
sicher: sie baut auf einen speziellen Marker-Typ auf — wenn er fehlt, passiert nichts an der Stelle
|
||||
</li>
|
||||
<li>
|
||||
effizient: da sie in einer Funktionstemplate-Instantiierung steht, wird sie nur im fraglichen Spezialfall getriggert
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
<node CREATED="1696978865091" ID="ID_1134916435" MODIFIED="1696978893087" TEXT="aber schön ist sie nicht — bestenfalls gut versteckt"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696979065849" ID="ID_1017963709" MODIFIED="1697023014065" TEXT="Umsetzung versuchen">
|
||||
<linktarget COLOR="#3551b4" DESTINATION="ID_1017963709" ENDARROW="Default" ENDINCLINATION="8;-31;" ID="Arrow_ID_855336150" SOURCE="ID_1356286451" STARTARROW="None" STARTINCLINATION="-132;6;"/>
|
||||
<icon BUILTIN="yes"/>
|
||||
<node CREATED="1697022864978" ID="ID_360386085" MODIFIED="1697022989269" TEXT="im Prinzip „ganz einfach“">
|
||||
<icon BUILTIN="ksmiletris"/>
|
||||
</node>
|
||||
<node CREATED="1697022883830" ID="ID_996336424" MODIFIED="1697022924582" TEXT="C++17 - Trick: std::invoke verwenden mit einem λ zum Tupel-Umbau">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
<node CREATED="1697022927591" ID="ID_1968515341" MODIFIED="1697022985926" TEXT="hab ein paar Stunden gebraucht umd den „ganz einfachen“ Aufruf richtig einzubauen">
|
||||
<icon BUILTIN="smiley-angry"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1696532404400" ID="ID_1917558827" MODIFIED="1696539443047" TEXT="Variante-2 : optional-Lifecycle">
|
||||
|
|
@ -65762,6 +65875,67 @@
|
|||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1696972878105" ID="ID_880873658" MODIFIED="1696972924189">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
allerdings: abgeleitete Klassen <b>leben gefährlich</b>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
<node CREATED="1696972938795" ID="ID_1194001800" MODIFIED="1696972960949" TEXT="wenn im Konstruktor etwas schiefgeht"/>
|
||||
<node CREATED="1696972967768" ID="ID_1795585703" MODIFIED="1696972989481" TEXT="dann gelingt nicht einmal die Heap-Allokation vollständig"/>
|
||||
<node CREATED="1696973111469" ID="ID_229806295" MODIFIED="1696973186990" TEXT="und der Destruktor blockt und scheitert nicht...">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Ein normaler Thread (und ein Joinable) würden den Aufruf des Destruktors verbieten, und damit die Applikation terminieren
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
<node CREATED="1696972994851" ID="ID_983723928" MODIFIED="1696973016584">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
aber der Thread <i>ist schon „unterwegs“</i>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<icon BUILTIN="clanbomber"/>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1696973047334" ID="ID_1485957927" MODIFIED="1696973196674" TEXT="Mitigation: nicht in die Thread-Funktion eintreten wenn nicht isAlive()">
|
||||
<icon BUILTIN="idea"/>
|
||||
<node CREATED="1696973209112" ID="ID_1135397788" MODIFIED="1696973227449" TEXT="die C++ Lib schließt diesen Fall eigentlich aus"/>
|
||||
<node CREATED="1696973227990" ID="ID_481018550" MODIFIED="1696976336073" TEXT="aber wenn jemand explizit detach() macht..."/>
|
||||
<node CREATED="1696973241644" ID="ID_1093970050" MODIFIED="1696973309225" TEXT="eigentlich müßte man diesen Fall auf einen »Autonomous Thread« einschränken">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
...aber das gibt mein Framework nicht her
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1696536197821" ID="ID_817844637" MODIFIED="1696536200313" TEXT="Variante-2">
|
||||
<node CREATED="1696536201285" ID="ID_688200807" MODIFIED="1696536223893" TEXT="die Analyse für Variante-1 gilt entsprechend"/>
|
||||
|
|
@ -66134,6 +66308,7 @@
|
|||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696966318864" ID="ID_1955344707" MODIFIED="1696966351181" TEXT="sauberes Memory-Management belegen">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<node CREATED="1696971867646" ID="ID_1915512401" MODIFIED="1696971888573" TEXT="lib/test/testdummy.hpp �� als Member verwenden"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696538825003" ID="ID_1787030725" MODIFIED="1696539451332" TEXT="ThreadWrapperLifecycle_test">
|
||||
|
|
|
|||
Loading…
Reference in a new issue