From d78211a9a11d192476d214d3ebfab65d54b058b1 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 24 Mar 2018 10:13:08 +0100 Subject: [PATCH] DI: implement C++11 solution of Double-Checked-Locking with std::atomic + Mutex This solution is considered correct by the experts. Regarding the dependency-configuration part, we do not care too much about performance and use the somewhat slower default memory ordering constraint --- src/lib/depend2.hpp | 44 +-- wiki/thinkPad.ichthyo.mm | 594 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 609 insertions(+), 29 deletions(-) diff --git a/src/lib/depend2.hpp b/src/lib/depend2.hpp index e29512db4..bef6a9c14 100644 --- a/src/lib/depend2.hpp +++ b/src/lib/depend2.hpp @@ -80,6 +80,7 @@ #include #include +#include #include @@ -172,8 +173,8 @@ namespace lib { using Factory = std::function; using Lock = ClassLock; - static SRV* instance; - static Factory factory; + static std::atomic instance; + static Factory factory; static InstanceHolder singleton; @@ -189,27 +190,26 @@ namespace lib { SRV& operator() () { - if (!instance) - retrieveInstance(); -// ENSURE (instance); - return *instance; + SRV* object = instance.load (std::memory_order_acquire); + if (!object) + { + Lock guard; + + object = instance.load (std::memory_order_relaxed); + if (!object) + { + if (!factory) + object = singleton.buildInstance(); + else + object = factory(); + factory = disabledFactory; + } + instance.store (object, std::memory_order_release); + } +// ENSURE (object); + return *object; } - private: - void - retrieveInstance() - { - Lock guard; - - if (!instance) - { - if (!factory) - instance = singleton.buildInstance(); - else - instance = factory(); - factory = disabledFactory; - } - } static SRV* disabledFactory() @@ -224,7 +224,7 @@ namespace lib { /* === allocate Storage for static per type instance management === */ template - SRV* Depend::instance; + std::atomic Depend::instance; template typename Depend::Factory Depend::factory; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 549d608b5..81ef10087 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -4897,7 +4897,7 @@ - + @@ -26912,7 +26912,7 @@ - + @@ -27347,7 +27347,7 @@ - + @@ -27408,15 +27408,346 @@ + + + + + + +

+ Thema: Memory access order constraints +

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

+ Grundidee: synchronizes-with-Beziehung herstellen auf Guard-Variable +

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

+ ...das meint zweierlei +

+
    +
  • + wir brauchen keine volle sequentielle Konsistenz +
  • +
  • + eigentlich würde consume statt acquire genügen,
    aber wir verzichten auf diesen ehr theoretischen Performance-Gewinn,
    welcher nur relevant wäre, wenn wir auf ARM einen modernen Compiler einsetzen +
  • +
+ + +
+
+ + + + + + +

+ essentiell ist, im Mutex-geschützten Bereich +

+

+ auf einer temporären lokalen Instanz-Variable zu arbeiten +

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

+ warum? +

+

+ weil per Definitionem dieses gesamte Konfigurations-Thema +

+

+ als nicht performance-kritisch eingestuft wird -- und ich mehr Wert darauf lege, +

+

+ die verschiedenen Belange im Quelltext nicht zu vermischen +

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

+ es gilt schlichtweg als Architektur-Fehler, wenn hier eine Kollision geschiet. +

+

+ Und es gibt keinen sinnvollen Weg, wie die Applikation dann weiterarbeiten kann. +

+

+ Daher werfen wir ja auch error::Fatal +

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

+ ...denn sonst könnte genau das gleiche Desaster passieren, +

+

+ das auch in fehlerhaftem Double-Checked-Locking auftritt +

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

+ ...d.h. das ganze Locking und die memory-order schützt uns hier überhaupt nicht! +

+

+ Es kann sehr wohl passieren, daß ein anderer Thread grade eben noch +

+

+ sicht den Pointer auf den Service geholt hat, und wir dann den Service zerstören, während +

+

+ der andere Thread ihn grade nutzt. +

+ + +
+
+ + + + + + +

+ unsere Architektur stellt aber sicher, +

+

+ daß dieser Fall nicht relevant ist +

+ + +
+ + + + + +

+ warum? +

+

+ Weil der "andere Thread" nur von zwei Subsystemen her kommen kann +

+
    +
  • + dem Subsystem selber, das auch den Service erzeugt.
    Beispiel ist eine UI-Interaktion aus dem Event-Loop thread +
  • +
  • + aus einem anderen Subsystem, das vom Serivce-Provider abhängt +
  • +
+

+ In beiden Fällen stellen unsere Prinzipien zum Betreiben von Subsystemen sicher, +

+

+ daß dieser "andere Thread" nicht (mehr) aktiv sein darf, wenn der Shutdown erfolgt. +

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

+ hier ist ein Segfault möglich +

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

+ ...denn wir sind auf x86_64 -- und diese Plattform ist per default fast überall sequentially coherent +

+ + +
-
-
- + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Konsequenz: das ist keine Library-Implementierung +

+ + +
+
+
+ + + + + + +
+ + + + + + + + + +
@@ -27476,6 +27807,18 @@ + + + + + + + + + + + + @@ -28025,7 +28368,7 @@ - + @@ -29048,6 +29391,243 @@ + + + + + + + + + + + + + + + +

+ das sind verschiedene Blickwinkel auf das gleiche Thema +

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

+ Bedeutung dieser Schreibweise: +

+
    +
  • + der erste Zugriff liegt vor der Barriere, der zweite danach +
  • +
  • + die Barriere garantiert jeweils nur, daß der zweitgenannte Zugriff nicht vor den erstgenannten verschoben werden kann +
  • +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ eine Solche konstituiert die synchronizes-with-Beziehuung +

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

+ Grundbeziehung: synchronizes-with +

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

+ das gilt nur im Rahmen der synchronizes-with-Beziehung +

+ + +
+
+ + + + + + +

+ das heißt, nur für einen vom gleichen Mutex geschützen  Bereich! +

+ + +
+
+ + + + + + +

+ In der naiven Implementierung greift der prüfende Thread auf die instance-Variable +

+

+ ohne jedwede Beziehung zum anderen Thread zu; er verwendet keinen Mutex und keinen Atomic. +

+

+ Und genau deshalb kann er das Setzen des Instanz-Pointers sehen, ohne daß eine +

+

+ Ordnungsbeziehung zur der restlichen Initialisierung oder lazy computation besteht. +

+

+ +

+

+ Fix: die Beziehung herstellen. Das ist verursacht stets zusätzliche Kosten. +

+

+ +

+

+ Allerdinsg nicht auf einer Plattform, die ohnehin sequentiell-konsistent ist. Wie "zum Beispiel" x86/64 +

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

+ ...haben wir es hier mit einem Pattern zu tun +

+ + +
+
+ + + + + + +

+ ich nenne es "synchronised visibility cones" +

+ + +
+
+ + + + + + +

+ Dieses errichtet die Fiktion, +

+

+ als würden wir nur auf einer gemeinsamen (shared) Instanz arbeiten. +

+

+ In Realität arbeiten mehrere Threads/Cores mit mehreren Entitäten +

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

+ ...und andernfalls überhaupt vermeiden, +

+

+ die shared zone anzufassen! +

+ + +
+
+
+