Library: rearrange launch into the base policy

this is a mere rearrangement of code (+lots of comments),
but helps to structure the overall construction better.

ThreadWrapper::launchThread() now does the actual work to build
the active std::thread object and assign it to the thread handle,
while buildLauncher is defined in the context of the constructors
and deals with wiring the functors and decaying/copying of arguments.
This commit is contained in:
Fischlurch 2023-10-09 04:09:38 +02:00
parent 2d7137e776
commit 8518cf1fa0
2 changed files with 67 additions and 16 deletions

View file

@ -129,6 +129,7 @@ namespace lib {
namespace thread {// Thread-wrapper base implementation...
using lib::meta::typeSymbol;
using std::function;
using std::forward;
using std::move;
using std::decay_t;
@ -178,6 +179,29 @@ namespace lib {
, threadImpl_{forward<ARGS> (args)... }
{ }
/** @internal actually launch the new thread.
* Deliberately the #threadImpl_ is created empty, to allow for complete
* initialisation of all the combined policy classes stacked on top
* @warning Start of the new thread _syncs-with_ the return from std::thread ctor.
* Thus -- in theory -- initialising members of derived classes after constructing
* a non-empty std::thread object would be *undefined behaviour*. In practice however,
* this is more of a theoretical problem, since the OS scheduler has a considerable
* latency, so that the code within the new thread typically starts executing with an
* delay of _at least 100µs_
* @remark non the less, the thread-wrapper framework circumvents this possible undefined behaviour,
* by first creating the threadImpl_ empty, and only later move-assigning the std::thread.
* @param invocation a tuple holding some invokable and possible arguments, together forming the
* threadFunction to be executed in the new thread.
*/
template<class...INVO>
void
launchThread (tuple<INVO...>&& invocation)
{
ASSERT (not isLive(), "Thread already running");
threadImpl_ = make_from_tuple<std::thread> (invocation);
};
/** detect if the currently executing code runs within this thread */
bool invokedWithinThread() const;
@ -344,29 +368,44 @@ namespace lib {
/**
* Build a λ actually to launch the given thread operation later,
* after the thread-wrapper-object is fully initialised.
* The member function #invokeThreadFunction will be run as top-level
* within the new thread, possibly handling errors, but delegating to
* the user-provided actual thread-operation
* @param args a invokable + further arguments
* @note the invokable and the arguments will be materialised/copied
* thereby decaying the given type; this is necessary, because
* these arguments must be copied into the new thread. This will
* fail to compile, if the given invokable can not be invoked
* with these copied (and decayed) arguments.
*/
template<class...INVO>
static auto
buildLauncher (INVO&& ...args)
{
// materialise functor and arguments as copy, to be handed over into the new thread
tuple<decay_t<INVO>...> argCopy{forward<INVO> (args)...};
return [invocation = move(argCopy)]
return [invocation = move(argCopy)] //Note: functor+args bound by-value into the λ
(ThreadLifecycle& wrapper)
{
auto threadArgs = tuple_cat (tuple{&ThreadLifecycle::invokeThreadFunction<INVO...>, &wrapper}
,move (invocation));
ASSERT (not wrapper.isLive());
wrapper.threadImpl_
= make_from_tuple<std::thread> (threadArgs);
};
{ //the thread-main function
wrapper.launchThread (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
}
/**
* Configuration builder to define the operation to run in the thread,
* and possibly configure further details, depending on the Policy used.
* @remark the primary ThreadLifecycle-ctor accepts such a Launch-instance
* and invokes the chain of λ-functions collected in the member #launch
*/
struct Launch
: util::MoveOnly
{
using Launcher = std::function<void(ThreadLifecycle&)>;
Launcher launch;
function<void(ThreadLifecycle&)> launch;
template<class FUN, typename...ARGS>
Launch (FUN&& threadFunction, ARGS&& ...args)

View file

@ -65704,7 +65704,7 @@
</html></richcontent>
</node>
<node COLOR="#435e98" CREATED="1696773922124" ID="ID_310300656" MODIFIED="1696774335094" TEXT="&#x27f9; bleibt nur der Einstieg &#xfc;ber eine statische Methode im Zieltyp"/>
<node CREATED="1696773947217" ID="ID_897608956" MODIFIED="1696774304294">
<node CREATED="1696773947217" ID="ID_897608956" MODIFIED="1696817454466">
<richcontent TYPE="NODE"><html>
<head>
@ -65726,7 +65726,7 @@
<font color="#1b0d7b" face="Monospaced" size="2">&#160;&#160;&#160;&#160;new&#160;ThreadHookable{ </font>
</p>
<p>
<font color="#1b0d7b" face="Monospaced" size="2">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;ThreadHookable::launch(myFunctor, arg) </font>
<font color="#1b0d7b" face="Monospaced" size="2">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;ThreadHookable::Launch(myFunctor, arg) </font>
</p>
<p>
<font color="#1b0d7b" face="Monospaced" size="2">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;.atStart(initHook) </font>
@ -65735,7 +65735,8 @@
<font color="#1b0d7b" face="Monospaced" size="2">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;.atEnd(terminationHook)}); </font>
</p>
</body>
</html></richcontent>
</html>
</richcontent>
<icon BUILTIN="idea"/>
</node>
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1696774321215" ID="ID_1696275940" MODIFIED="1696774330486" TEXT="Suche nach sonstigen Alternativen vertagt">
@ -65801,13 +65802,24 @@
</node>
<node CREATED="1696812766502" ID="ID_124892301" MODIFIED="1696812776928" TEXT="Thread-start funktioniert, auch mit Parameter"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696812791066" ID="ID_1789709297" MODIFIED="1696812798545" TEXT="Code sinnvoll anordnen">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1696812791066" ID="ID_1789709297" MODIFIED="1696817507333" TEXT="Code sinnvoll anordnen">
<icon BUILTIN="pencil"/>
<node CREATED="1696813183208" ID="ID_100161158" MODIFIED="1696813227123" TEXT="jetzt noch die invokeThreadFunctor-Memberfunktion mit in das Tupel packen..."/>
<node CREATED="1696813171800" ID="ID_155742882" MODIFIED="1696813229500" TEXT="Ha! std::make_from_tuple">
<icon BUILTIN="idea"/>
</node>
<node COLOR="#338800" CREATED="1696816614379" ID="ID_1380705903" MODIFIED="1696816646902" TEXT="das Starten und Zuweisen in eine Hilfsfunktion &#x2b0a; in die ThreadWrapper-Basisklasse">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#6e4398" CREATED="1696817088763" ID="ID_1405496774" MODIFIED="1696817115630" TEXT="so langsam nimmt dieses Konstrukt eine Form an, mit der ich leben kann...">
<icon BUILTIN="smiley-neutral"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696817478656" ID="ID_1607424036" MODIFIED="1696817494294" TEXT="Umbau der bestehenden Konstruktoren">
<icon BUILTIN="flag-yellow"/>
<node CREATED="1696817511875" ID="ID_5608930" MODIFIED="1696817549173" TEXT="zwar wird weiterhin der Konstruktor mit threadID + Funktor am meisten verwendet"/>
<node CREATED="1696817550022" ID="ID_1116307132" MODIFIED="1696817567896" TEXT="...aber der neue Konstruktor mit Builder wird nun zum Basis-Fall"/>
</node>
</node>
</node>