Library: identified design challenges

On a close look, the wrapper design as pursued here
turns out to be prone to insidious data race problems.
This was true also for the existing solution, but becomes
more clear due to the precise definitions from the C++ standard.

This is a confusing situation, because these races typically do not
materialise in practice; due to the latency of the OS scheduler the
new thread starts invoking user code at least 100µs after the Wrapper
object is fully constructed (typically more like 500µs, which is a lot)

The standard case (lib::Thread) in its current form is correct, but borderline
to undefined behaviour, and any initialisation of members in a derived class
would be off limits (the thread-wrapper should not be used as baseclass,
rather as member)
This commit is contained in:
Fischlurch 2023-10-07 03:25:39 +02:00
parent 88b91d204c
commit 08c3e76f14
3 changed files with 218 additions and 13 deletions

View file

@ -68,7 +68,7 @@ namespace test{
namespace {
constexpr size_t DEFAULT_RUNS = 10'000'000;
constexpr double SCALE = 1e6; // Results are in µ-sec
using CLOCK_SCALE = std::micro; // Results are in µ-sec
}
@ -83,12 +83,12 @@ namespace test{
benchmarkTime (FUN const& invokeTestLoop, const size_t repeatCnt = DEFAULT_RUNS)
{
using std::chrono::system_clock;
using Dur = std::chrono::duration<double>;
using Dur = std::chrono::duration<double, CLOCK_SCALE>;
auto start = system_clock::now();
invokeTestLoop();
Dur duration = system_clock::now () - start;
return duration.count()/(repeatCnt) * SCALE;
return duration.count()/(repeatCnt);
};
@ -154,7 +154,7 @@ namespace test{
threadBenchmark(FUN const& subject, const size_t repeatCnt = DEFAULT_RUNS)
{
using std::chrono::system_clock;
using Dur = std::chrono::duration<double>;
using Dur = std::chrono::duration<double, CLOCK_SCALE>;
// the test subject gets the current loop-index and returns a checksum value
ASSERT_VALID_SIGNATURE (decltype(subject), size_t(size_t));
@ -194,7 +194,7 @@ namespace test{
checksum += thread.checksum;
}
double micros = sumDuration.count() / (nThreads * repeatCnt) * SCALE;
double micros = sumDuration.count() / (nThreads * repeatCnt);
return std::make_tuple (micros, checksum);
}

View file

@ -30,6 +30,7 @@
#include "lib/iter-explorer.hpp"
#include "lib/scoped-collection.hpp"
#include "lib/test/microbenchmark.hpp"
#include "lib/test/diagnostic-output.hpp"
#include <atomic>
#include <chrono>
@ -40,6 +41,7 @@ using std::atomic_uint;
using std::this_thread::yield;
using std::this_thread::sleep_for;
using std::chrono::microseconds;
using std::chrono::system_clock;
namespace lib {
@ -49,6 +51,8 @@ namespace test{
const uint NUM_THREADS = 200;
const uint REPETITIONS = 10;
using CLOCK_SCALE = std::micro; // Results are in µ-sec
}
@ -73,11 +77,19 @@ namespace test{
void
defaultWrapperLifecycle()
{
atomic_uint i{0};
Thread thread("counter", [&]{ ++i; }); // bind a λ and launch thread
while (thread) yield(); // ensure thread has finished and detached
using Dur = std::chrono::duration<double, CLOCK_SCALE>;
using Point = system_clock::time_point;
Point threadStart;
Point afterCtor;
Thread thread("lifecycle", [&]{
threadStart = system_clock::now();
});
afterCtor = system_clock::now();
while (thread) yield();
CHECK (i == 1); // verify the effect has taken place
double offset = Dur{threadStart - afterCtor}.count();
SHOW_EXPR(offset)
CHECK (offset > 0);
UNIMPLEMENTED ("demonstrate state change");
}

View file

@ -65253,7 +65253,8 @@
</body>
</html></richcontent>
</node>
<node CREATED="1696532676404" ID="ID_91266848" MODIFIED="1696532680013" TEXT="Front-End">
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1696532676404" ID="ID_91266848" MODIFIED="1696624166370" TEXT="Front-End">
<icon BUILTIN="flag-pink"/>
<node CREATED="1696532680835" ID="ID_553798887" MODIFIED="1696536376316">
<richcontent TYPE="NODE"><html>
<head/>
@ -65265,7 +65266,7 @@
</html></richcontent>
</node>
<node CREATED="1696532733604" ID="ID_1013220634" MODIFIED="1696532744490" TEXT="und kann ihn nur starten mit einer statischen Factory-Funktion"/>
<node CREATED="1696536644665" ID="ID_1557332698" MODIFIED="1696536798494" TEXT="Beschlu&#xdf;: Storage ist inline und an die Lebensdauer gekoppelt">
<node COLOR="#5b280f" CREATED="1696536644665" ID="ID_1557332698" MODIFIED="1696624156275" TEXT="Beschlu&#xdf;: Storage ist inline und an die Lebensdauer gekoppelt">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
@ -65274,9 +65275,10 @@
</p>
</body>
</html></richcontent>
<icon BUILTIN="button_cancel"/>
</node>
</node>
<node CREATED="1696536837512" ID="ID_1958442975" MODIFIED="1696536975757" TEXT="Lebenszyklus-Zustand wird atomar verwaltet (acquire_release)">
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1696536837512" ID="ID_1958442975" MODIFIED="1696624184191" TEXT="Lebenszyklus-Zustand wird atomar verwaltet (acquire_release)">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
@ -65285,6 +65287,7 @@
</p>
</body>
</html></richcontent>
<icon BUILTIN="flag-pink"/>
</node>
</node>
<node CREATED="1696532838782" ID="ID_46766207" MODIFIED="1696532858447" TEXT="der Konflikt im Lebensdauer-Thema ist damit beigelegt">
@ -65312,6 +65315,196 @@
</node>
</node>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#ab1d25" CREATED="1696620911198" ID="ID_1727345298" MODIFIED="1696620937599" TEXT="Kritik (n&#xe4;chster Tag)">
<icon BUILTIN="stop-sign"/>
<node CREATED="1696620943304" ID="ID_211890661" MODIFIED="1696620971910" TEXT="gestern nur in fertigen Komponenten gedacht &#x2014; das war zu kurz gegriffen">
<icon BUILTIN="broken-line"/>
<node CREATED="1696620974692" ID="ID_408561702" MODIFIED="1696621055594" TEXT="jede der beiden Varianten impliziert eine abgeleitete Klasse">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
...sonst w&#228;re das ganze Unterfangen theoretischer Natur; nur dadurch wird es interessant, Storage zu verwalten und einen Zugang freizuschalten
</p>
</body>
</html>
</richcontent>
</node>
<node CREATED="1696621067492" ID="ID_1848245258" MODIFIED="1696621085856" TEXT="der Session-Thread braucht einen smart-ptr als Front-End">
<node CREATED="1696621086981" ID="ID_427538750" MODIFIED="1696621153920" TEXT="1. es ist ein PImpl (mit eigenem API)"/>
<node CREATED="1696621100107" ID="ID_1434548951" MODIFIED="1696621148849" TEXT="2. und die Impl h&#xe4;lt den Thread als Member"/>
<node CREATED="1696621167042" ID="ID_558727357" MODIFIED="1696621177756" TEXT="3. Zugang wird eigens &#xfc;ber Monitor gesch&#xfc;tzt">
<node CREATED="1696621182464" ID="ID_813154978" MODIFIED="1696621189603" TEXT="essentiell f&#xfc;r die Logik"/>
<node CREATED="1696621190455" ID="ID_613730831" MODIFIED="1696621206993" TEXT="stets nur eine Aktion in Transit"/>
</node>
</node>
<node COLOR="#611224" CREATED="1696621234330" ID="ID_481499226" MODIFIED="1696621321463" TEXT="&#x27f9; in beiden F&#xe4;llen gelingt es nicht, alles in eine einzige opaque Komponente zu packen"/>
</node>
<node CREATED="1696621353177" ID="ID_1935311062" MODIFIED="1696621378012" TEXT="was ich aber dennoch unbedingt realisieren m&#xf6;chte: Ordnung der Lifecycle-Klammern">
<icon BUILTIN="yes"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696621626749" ID="ID_1402437857" MODIFIED="1696621682930" TEXT="Konsequenz: callback-Hooks im Lifecycle bereitstellen">
<icon BUILTIN="yes"/>
<node CREATED="1696621694652" ID="ID_489753305" MODIFIED="1696621702239" TEXT="das wirft Probleme auf...">
<node CREATED="1696621703203" ID="ID_1346123872" MODIFIED="1696621709128" TEXT="brauche daf&#xfc;r zus&#xe4;tzliche Storage"/>
<node CREATED="1696621709770" ID="ID_1531634936" MODIFIED="1696621727446" TEXT="Initialisierung vor Thread-Start">
<node CREATED="1696621761419" ID="ID_1692673814" MODIFIED="1696621789787" TEXT="das hei&#xdf;t: lazy builder oder ctor-Args"/>
<node CREATED="1696621792391" ID="ID_1048165021" MODIFIED="1696621801098" TEXT="Lesbarkeit ist ebenfalls ein Problem"/>
<node CREATED="1696623868041" ID="ID_1255108830" MODIFIED="1696623893825" TEXT="und: wenig Spielraum">
<node CREATED="1696623897565" ID="ID_787579556" MODIFIED="1696623905791" TEXT="Thread ist ohnehin schon (zu) komplex"/>
<node CREATED="1696623906508" ID="ID_373461507" MODIFIED="1696623932988" TEXT="es gibt eine Variadic-Argumentliste mit perfect-forwarding"/>
<node CREATED="1696623940239" ID="ID_1472826799" MODIFIED="1696623960000" TEXT="bisweilen ohnehin schon Typdeduktions-Probleme"/>
<node CREATED="1696623960757" ID="ID_400335187" MODIFIED="1696623997084" TEXT="die Typen werden auch noch durch zwei Ebenen druchgegeben und &quot;decayed&quot;"/>
<node CREATED="1696623997956" ID="ID_801709365" MODIFIED="1696624021191" TEXT="und der Thread-Start steckt in der Basisklasse"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1696624455507" ID="ID_771623143" MODIFIED="1696640533961">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
&#10233;&#160;<i>Initialisierung</i>&#160;von Policy-Bausteinen <i>ist nicht m&#246;glich</i>
</p>
</body>
</html>
</richcontent>
<arrowlink COLOR="#fe225e" DESTINATION="ID_1852065850" ENDARROW="Default" ENDINCLINATION="147;-294;" ID="Arrow_ID_1803073395" STARTARROW="None" STARTINCLINATION="-332;18;"/>
<node CREATED="1696624710262" ID="ID_798050443" MODIFIED="1696624792744" TEXT="Konsequenz: ThreadJoinable ist (latent) racy">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
(seufz)
</p>
<p>
<i>streng genommen</i>&#160;k&#246;nnte die Thread-Funktion bereits mit der Initialisierung des lib::Result beginnen, w&#228;hrend ihr dann die default-Initialisierung dazwischenfunkt
</p>
</body>
</html>
</richcontent>
</node>
<node CREATED="1696624920612" ID="ID_1218441049" MODIFIED="1696624940325" TEXT="...und jede Erweiterung, die Daten in der Policy hat, ebenfalls"/>
</node>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696628443151" ID="ID_1852065850" MODIFIED="1696640533961" TEXT="das Race-Problem stellt das ganze Design in Frage">
<linktarget COLOR="#fe225e" DESTINATION="ID_1852065850" ENDARROW="Default" ENDINCLINATION="147;-294;" ID="Arrow_ID_1803073395" SOURCE="ID_771623143" STARTARROW="None" STARTINCLINATION="-332;18;"/>
<icon BUILTIN="broken-line"/>
<node CREATED="1696628561935" ID="ID_183158925" MODIFIED="1696628612622" TEXT="so ziemlich jede Form von Layering und Composition ist betroffen">
<icon BUILTIN="closed"/>
</node>
<node CREATED="1696628634743" ID="ID_860811090" MODIFIED="1696639826869" TEXT="andererseits ist das Problem sehr wahrscheinlich theoretischer Natur">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
da erfahrungsgem&#228;&#223; der OS-Scheduler immer <i>eine gewisse</i>&#160;Latenz hat, bis ein Thread tats&#228;chlich ausf&#252;hrbar wird
</p>
</body>
</html>
</richcontent>
<node COLOR="#338800" CREATED="1696628723995" ID="ID_1617045707" MODIFIED="1696639807086" TEXT="nochmal Beobachtungen machen">
<icon BUILTIN="button_ok"/>
<node CREATED="1696639643860" ID="ID_82825911" MODIFIED="1696639649431" TEXT="std::system_clock"/>
<node CREATED="1696639649973" ID="ID_136142260" MODIFIED="1696639668596" TEXT="Zeitpunkte erfassen"/>
<node CREATED="1696639669120" ID="ID_580835805" MODIFIED="1696639673243" TEXT="am Ende vergleichen"/>
</node>
<node COLOR="#435e98" CREATED="1696639673735" ID="ID_125089777" MODIFIED="1696639838019" TEXT="&#x27f9; Thread-Funktion startet stets &gt; 100&#xb5;s nach dem Ende des ctors">
<node CREATED="1696639722881" ID="ID_1727068743" MODIFIED="1696639726364" TEXT="das ist eine Menge"/>
<node CREATED="1696639726816" ID="ID_1384372347" MODIFIED="1696639753848" TEXT="(i.d.R sehe ich sogar &#x2248; 500&#xb5;s)"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1696639754464" ID="ID_279169592" MODIFIED="1696639859888">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
und trotzdem: das ist <b>kein Gegenbeweis</b>
</p>
</body>
</html></richcontent>
<icon BUILTIN="stop-sign"/>
</node>
<node CREATED="1696639771290" ID="ID_1990815963" MODIFIED="1696639789477" TEXT="...sondern &#x201e;undefined behaviour in action&#x201c;"/>
</node>
</node>
<node CREATED="1696628756484" ID="ID_602079301" MODIFIED="1696629021008">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
man k&#246;nnte das Thema entsch&#228;rfen,
</p>
<p>
indem man das Thread-Handle zun&#228;chst leer initialisiert
</p>
<p>
und dann erst ganz oben im ctor-chain den Thread startet
</p>
<p>
und per move-assign auf das Handle schiebt.
</p>
</body>
</html>
</richcontent>
<icon BUILTIN="idea"/>
<node CREATED="1696628821820" ID="ID_1115877978" MODIFIED="1696628844773" TEXT="std::thread is default constructible &#x27f6; inaktiv"/>
<node CREATED="1696628845625" ID="ID_1290073525" MODIFIED="1696628855357" TEXT="und es gibt einen move-assignment-Operator"/>
<node CREATED="1696629037023" ID="ID_1091879283" MODIFIED="1696629059464" TEXT="man k&#xf6;nnte dann zumindest bei diesem Policy-based-Design bleiben"/>
<node CREATED="1696629077442" ID="ID_955648903" MODIFIED="1696629089884" TEXT="und immerhin auch Member in den Policies initialisieren"/>
</node>
<node COLOR="#435e98" CREATED="1696634919834" ID="ID_1703278814" LINK="https://stackoverflow.com/q/77247752/444796" MODIFIED="1696640406805" TEXT="sicherheitshalber auch nochmal auf Stackoverflow gefragt">
<icon BUILTIN="info"/>
<node COLOR="#338800" CREATED="1696639626943" ID="ID_167373706" MODIFIED="1696640356700" TEXT="mehrere Antworten und alle best&#xe4;tigen mein Argument">
<icon BUILTIN="button_ok"/>
</node>
<node BACKGROUND_COLOR="#e4ddaa" COLOR="#435e98" CREATED="1696639898702" ID="ID_1364351681" MODIFIED="1696640392300" TEXT="einer hat ebenfalls die L&#xf6;sung mit dem move-assign vorgeschlagen">
<icon BUILTIN="ksmiletris"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff00bd" CREATED="1696639942907" ID="ID_773629884" MODIFIED="1696639967679" TEXT="und was nun?">
<font NAME="SansSerif" SIZE="13"/>
<icon BUILTIN="smiley-angry"/>
<node CREATED="1696639992869" ID="ID_237693887" MODIFIED="1696639998160" TEXT="sehe folgende M&#xf6;glichkeiten">
<node CREATED="1696640023536" ID="ID_470066396" MODIFIED="1696640047849" TEXT="&#x201e;ich hab mich verrant&#x201c; &#x27f9; das Wrapper-Design aufgeben">
<node CREATED="1696640075401" ID="ID_1516484124" MODIFIED="1696640085620" TEXT="stets std::thread als direktes Member verwenden"/>
<node CREATED="1696640111893" ID="ID_25607343" MODIFIED="1696640134822" TEXT="per anderweitiger Struktur f&#xfc;r ausreichende Lebensdauer sorgen"/>
<node CREATED="1696640139849" ID="ID_1633568429" MODIFIED="1696640159524" TEXT="Werte in Atomics ablegen und austauschen"/>
</node>
<node CREATED="1696640180387" ID="ID_1878688951" MODIFIED="1696640198386" TEXT="die Wrapper auf das anfangs definierte Minimum beschr&#xe4;nken">
<node CREATED="1696640214151" ID="ID_548509988" MODIFIED="1696640256973" TEXT="typischerweise auf Subsystem-Ebene und dort eine member-Var"/>
<node CREATED="1696640278102" ID="ID_362520772" MODIFIED="1696640302706" TEXT="Thread (Standardfall) so &#xe4;ndern, da&#xdf; es im dtor intern ein detach() macht"/>
<node CREATED="1696640304051" ID="ID_838020700" MODIFIED="1696640339522" TEXT="daf&#xfc;r f&#xe4;llt detach() auf dem API weg und der SteamDispatcher bleibt wie er war"/>
</node>
<node CREATED="1696640573023" ID="ID_1848128254" MODIFIED="1696640645673" TEXT="Komplexit&#xe4;t akzeptieren und das Baukasten-System erweitern">
<node CREATED="1696640688951" ID="ID_308046854" MODIFIED="1696640722766" TEXT="Leistungen">
<node CREATED="1696640723816" ID="ID_1672854598" MODIFIED="1696640771200" TEXT="anheben der Abstraktions-Ebene"/>
<node CREATED="1696640729546" ID="ID_1634619318" MODIFIED="1696640763097" TEXT="explizit und klar in der Verwendung"/>
</node>
<node CREATED="1696640782282" ID="ID_808167181" MODIFIED="1696640793141" TEXT="Konfigurations-Schema einf&#xfc;hren">
<node CREATED="1696640794928" ID="ID_32135588" MODIFIED="1696640811229" TEXT="siehe ElementBoxWidget &#x27f9; BuilderQualifierSupport"/>
<node CREATED="1696641111343" ID="ID_605754599" MODIFIED="1696641133479" TEXT="dieses erweitern um positionale Argumente (Filter)"/>
<node CREATED="1696641170367" ID="ID_1556266623" MODIFIED="1696641175588" TEXT="im Konstruktor-Rumpf">
<node CREATED="1696641176734" ID="ID_1085483271" MODIFIED="1696641183329" TEXT="zuerst die Konfig auswerten"/>
<node CREATED="1696641183773" ID="ID_1987401059" MODIFIED="1696641191081" TEXT="dann explizit den Thread starten"/>
</node>
</node>
<node CREATED="1696641206002" ID="ID_284758018" MODIFIED="1696641255688" TEXT="Ausblick auf die Zukunft: Priority-Klassen, Supervisor anbinden"/>
</node>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696533317806" ID="ID_641021700" MODIFIED="1696533328356" TEXT="Sicherheit">
<icon BUILTIN="yes"/>
@ -81752,7 +81945,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1696539035824" ID="ID_1472214443" MODIFIED="1696539109792" TEXT="generische L&#xf6;sung f&#xfc;r dieses Nutz-Muster">
<linktarget COLOR="#696992" DESTINATION="ID_1472214443" ENDARROW="Default" ENDINCLINATION="-24;-75;" ID="Arrow_ID_409292846" SOURCE="ID_1071243890" STARTARROW="None" STARTINCLINATION="-43;3;"/>
<icon BUILTIN="flag-yellow"/>
<node CREATED="1696539114617" ID="ID_104020495" MODIFIED="1696539443047" TEXT="OptionalThread">
<node CREATED="1696539114617" ID="ID_104020495" MODIFIED="1696620523042" TEXT="ThreadHookable">
<arrowlink COLOR="#898bab" DESTINATION="ID_1917558827" ENDARROW="Default" ENDINCLINATION="-1027;99;" ID="Arrow_ID_1553665185" STARTARROW="None" STARTINCLINATION="532;77;"/>
<icon BUILTIN="info"/>
<node CREATED="1696539237403" HGAP="36" ID="ID_922406521" LINK="#ID_137837629" MODIFIED="1696539267790" TEXT="vgl. auch Situation f&#xfc;r Gtk-Lumiera" VSHIFT="6">