[MSVC 2013] Function local static zero-init

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
Artificial Mind
Establishment
Beiträge: 802
Registriert: 17.12.2007, 17:51
Wohnort: Aachen

[MSVC 2013] Function local static zero-init

Beitrag von Artificial Mind »

Hallo liebe Community!

Wir haben ein interessantes C++ Problem mit scoped static variables.
Zuerst einmal: wir nutzen Visual Studio 2013 ohne November CTP (dieses macht nämlich an anderer Stelle Probleme), weswegen scoped static initialization nicht threadsafe ist.
Insbesondere ist folgender Code eine Race Condition (die uns auch tatsächlich in der Anwendung diverse non-deterministische Access Violations liefert):
(Der Code wird natürlich aus diversen Thread parallel aufgerufen)

Code: Alles auswählen

foo()
{
   static A* a = bar();
   ... a nutzen
}
Es wird noch schlimmer, wir haben hunderte solcher statisch-lokalen Variablen (durch ein Macro).
(Unter gcc und clang ist natürlich alles ok, die implementieren das threadsafe init von C++11 bereits korrekt)

Da nach der ersten Initialisierung kaum noch Aufwand auf diese Codestelle entfällt, kam mir die Idee, ein Spinlock zum sichern zu nehmen:

Code: Alles auswählen

static std::atomic_flag lock = ATOMIC_FLAG_INIT; // non-function scope

foo()
{
   while (lock.test_and_set(std::memory_order_acquire))  { } // spin
   static A* a = bar();
   lock.clear(std::memory_order_release);
   ... a nutzen
}
Das funktioniert zwar, benötigt aber eigentlich für jede static variable einen eigenen Lock, den ich ja wegen des Problems auch nicht als static mit in die Funktion packen kann (eigentlich).
Insbesondere ist das für unser Macro dann sehr unangenehm, außerhalb der Funktionen noch die Locks zu erstellen.

Allerdings ist ATOMIC_FLAG_INIT ein #define auf {0} und so wie die Implementierung von atomic_flag im VS 13 aussieht, müsste das astreine zero-initialization sein.

Jetzt die Frage: kann man vll doch einfach threadsafe static den lock zero-initialisieren und dann als spinlock verwenden?

Oder offener: hat jemand eine bessere Idee mit dem Problem umzugehen?
Benutzeravatar
Artificial Mind
Establishment
Beiträge: 802
Registriert: 17.12.2007, 17:51
Wohnort: Aachen

Re: [MSVC 2013] Function local static zero-init

Beitrag von Artificial Mind »

Nachdem mir Cat nochmal die Grundzüge von zero-init erklärt hat, bin ich momentan bei folgendem Konstrukt:

Code: Alles auswählen

foo()
{
   static std::atomic_flag lock = ATOMIC_FLAG_INIT; // zero-init, keine race-condition
   while (lock.test_and_set(std::memory_order_acquire)) {} // spin
   static void* ptr = expensiveInit();
   lock.clear(std::memory_order_release);

   ... ptr nutzen
}
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [MSVC 2013] Function local static zero-init

Beitrag von CodingCat »

Ich habe daraufhin gerade etwas recherchiert, hier nochmal die Zusammenfassung: Global, static und local static Initialisierungen laufen prinzipiell in 3 Phasen:
Zero Initialization (typunabhängig ist immer aller Speicher genullt bevor irgendetwas passiert)
Constant Initialization (POD-Typen mit konstanten Ausdrücken initialisiert bevor irgendetwas passiert)
Dynamic Initialization (alles was übrig bleibt zur Laufzeit initialisiert, bei local static zum Zeitpunkt der Erstpassierung, innerhalb einer Übersetzungseinheit in Deklarationsreihenfolge, sonst unspezifizierte Reihenfolge, auch Templates unspezifizert)

Soweit zu den Garantien, Zero und Constant Initialization sind garantiert statische Initialisierungen und damit automatisch thread-safe. Dynamic Initialization ist vor C++11 und in unvollständigen C++11-Implementierungen (d.h. in allen derzeitigen Microsoft-Compilern) nicht thread-safe. Dynamic Initialization kann teilweise aus Optimierungsgründen in Static Initialization umgewandelt werden, darauf darf man sich bzgl. Thread-Safety aber nicht verlassen.

Für std::atomic_flag gibt C++11 eine eigene Garantie, dass der Initialisierungsausdruck std::atomic_flag lock = ATOMIC_FLAG_INIT immer zu statischer Initialisierung führt und somit im hier diskutierten Problemfall als thread-safe angenommen werden darf. Tatsächlich halten sich auch die Microsoft-Compiler durch ihre entsprechenden Standardbibliotheksimplementierungen (POD + konstanter Ausdruck) bereits daran.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
kimmi
Moderator
Beiträge: 1405
Registriert: 26.02.2009, 09:42
Echter Name: Kim Kulling
Wohnort: Luebeck
Kontaktdaten:

Re: [MSVC 2013] Function local static zero-init

Beitrag von kimmi »

FYI: Hier bei Stackoverflow: http://stackoverflow.com/questions/1780 ... c-variable

Gruß Kimmi
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [MSVC 2013] Function local static zero-init

Beitrag von CodingCat »

kimmi hat geschrieben:FYI: Hier bei Stackoverflow: http://stackoverflow.com/questions/1780 ... c-variable
Ja, krankt aber wie so viele Beiträge im Internet zum Thema Programmiersprachen am Mangel harter Bedingungen und Garantien und taugt damit leider allenfalls für eine löchrige Intuition.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Helmut
Establishment
Beiträge: 237
Registriert: 11.07.2002, 15:49
Wohnort: Bonn
Kontaktdaten:

Re: [MSVC 2013] Function local static zero-init

Beitrag von Helmut »

Artificial Mind hat geschrieben:Nachdem mir Cat nochmal die Grundzüge von zero-init erklärt hat, bin ich momentan bei folgendem Konstrukt:

Code: Alles auswählen

foo()
{
   static std::atomic_flag lock = ATOMIC_FLAG_INIT; // zero-init, keine race-condition
   while (lock.test_and_set(std::memory_order_acquire)) {} // spin
   static void* ptr = expensiveInit();
   lock.clear(std::memory_order_release);

   ... ptr nutzen
}
Das hat allerdings den Nachteil, dass die Funktion synchronisiert, wenn die Initialisierung bereits abgeschlossen ist. Und das dürfte ja der Großteil der Aufrufe sein. Ich würde folgendes empfehlen:

Code: Alles auswählen

foo()
{
   static void* ptr = 0;
   if(ptr == 0)
   {
       static std::atomic_flag lock = ATOMIC_FLAG_INIT; // zero-init, keine race-condition
       while (lock.test_and_set(std::memory_order_acquire)) {} // spin
       if (ptr == 0) ptr = expensiveInit();
       lock.clear(std::memory_order_release);
   }

   ... ptr nutzen
}
Einzige Voraussetzung ist, dass expensiveInit() nicht null zurückgeben kann.
Benutzeravatar
Artificial Mind
Establishment
Beiträge: 802
Registriert: 17.12.2007, 17:51
Wohnort: Aachen

Re: [MSVC 2013] Function local static zero-init

Beitrag von Artificial Mind »

Uh, gute Idee, mal gucken ob ich das in unser Macro einbauen kann.

Die Synchronisierung ist ja zum Glück nach dem Initialisieren nur noch minimalst (deswegen ein spinlock), deswegen fiel das noch nicht so stark ins Gewicht.
Aber besser ist durchaus besser ;)
Antworten