static Variable per Funktion initialisieren



  • Simon2 schrieb:

    Falls Dir eine andere sichere und schöne/einfache Lösung einfällt, freue ic h mich natürlich darüber...

    Klar 🙂
    boost::call_once()
    http://www.boost.org/doc/libs/1_39_0/doc/html/thread/synchronization.html#thread.synchronization.once

    #include "stdafx.h"
    
    #include <boost/thread/once.hpp>
    #include <boost/lambda/lambda.hpp>
    #include <boost/lambda/bind.hpp>
    #include <iostream>
    
    template <typename T> T f(T def)
    {
    	size_t ret = def;
    	// ...
    	return ret;
    }
    
    void g()
    {
    	static boost::once_flag guard = BOOST_ONCE_INIT;
    	static size_t initVal;
    
    	{
    		using namespace boost::lambda;
    		boost::call_once(guard, var(initVal) = bind(&f<unsigned long>, 7ul));
    	}
    
    	// ... initVal verwenden
    
    	std::cout << "initVal = " << initVal << "\n";
    
    	return;
    }
    

    OK, über schön und einfach lässt sich streiten 😃
    Aber so funktioniert das. Threadsafe.

    p.S.: call_once ist *relativ* billig, vor allem unter Windows. Natürlich kommt aber noch dazu, dass jedesmal trotzdem der Funktor zusammengebaut wird (auch wenn er nur 1x ausgeführt wird). Das wird zwar nicht wahnsinnig teuer sein, aber ich wollte es erwähnt haben. Sozusagen. 🕶



  • OK,

    danke ... allerdings fehlen mir die Zeit und Erfahrung, um Boost in unsere Entwicklungsumgebung einzubringen.
    Du erwähnst die Performance unter Windows ... ich bräuchte es unter AIX. Wird vermutlich kein Problem sein, weil Boost IIRC intensiven Gebrauch von generischer Programmierung macht, aber das ist ein zusätzlicher Risikofaktor.
    => Ich bleibe wohl beim n-fachen Initialisieren (was zeigt, dass Performance keine sooo große Rolle spielt in meinem Umfeld).

    Aber als zukünftige Lösung werde ich das im Blick behalten.

    Danke,

    Simon2.



  • Hi

    nochmal eine Nachfrage: Besteht das Threadingproblem eigentlich auch bei *husthust* globalen Variablen *husthust* ?
    Mir würde es auch reichen, die Variablen beim Programmstart zu initialisieren ...

    Ich stelle nämlich gerade fest, dass mein Compiler sich offensichtlich am const stört und nicht (nur) am static.
    Folgendes zeigt nämlich dasselbe Verhalten:

    void g() {
       size_t const initVal = f(7ul);
    ...
    

    und beknackterweise "funktioniert" das dagegen:

    void g() {
       size_t dummy = f(7ul);
       size_t const initVal = dummy;
    ...
    

    static alleine reicht aber auch schon aus für das Fehlverhalten - ich werd' bekloppt!

    😮 😮

    Gruß,

    Simon2.



  • Grundsätzlich besteht auch bei globalen Variablen dasselbe Problem, wenn diese über eine Funktion initialisiert werden.

    Der Standard schreibt IIRC nicht vor, dass alle globale Variablen beim Programmstart initialisiert werden, sondern bloss, dass das geschieht, bevor die erste Funktion in der entsprechenden Translation-Unit ausgeführt wird.
    Demzufolge könnte das mitten im Programm passieren, wo u.U. schon haufenweise Threads laufen.

    Die meisten Compiler handhaben es aber so, dass sämtliche globalen Daten initialisiert werden, "bevor das Programm losläuft".

    Solange du also in der Initialisierungsphase keine Threads startest, und wenn dein AIX Compiler das so macht wie "alle anderen", dann dürfte es kein Problem geben.

    Ansonsten: einfach eine "init()" Funktion machen, und die vor Programmstart aufrufen.



  • hustbaer schrieb:

    ...bevor die erste Funktion in der entsprechenden Translation-Unit ausgeführt wird....

    Naja, ich hatte mir das schon so gedacht, dass ich die globalen Variablen in "main.cpp" packe und dort initialisiere.
    Alle weiteren Threads werden frühestens von main() erzeugt. Das sollte also klappen - bis auf die Tatsache, dass die init-Funktion argv/argc braucht ... und die gibt's nunmal nicht vor main()-Call.

    Häßlich finde ich aber schon noch die Kopplung, die dadurch entsteht (jede konfigurierbare Kleinigkeit muss wieder in der main eingepflegt werde) ... und auch für "const" muss man schon ein wenig fummeln.
    Das Ganze lohnt sich IMHO bei mir nicht so recht, weil selbst mein "init" schon nur noch aus einer std::map liest, die beim ersten Zugriff aus einem Konf-File gefüllt wurde.
    Da ist mir das "const" schon noch wichtiger ... (wirklich ärgerlich, dass das nicht geht).

    hustbaer schrieb:

    ...eine "init()" Funktion machen, und die vor Programmstart aufrufen.

    😮 Wie geht das denn? 😮

    nee. ich denke, ich weiß schon, wie Du's meintest. 😉

    Gruß,

    Simon2.



  • Hi,

    nochmal als Nachtrag, falls irgendwann mal jemand ein ähnliches Phänomen hat und auf diesen Thread stößt 😉 ): In der Doku zum Compiler steht:

    XL C/C++ V9.0 for AIX schrieb:

    -O3 also performs higher and more aggressive optimizations that have the potential to slightly alter the semantics of your program.

    ... bin noch nicht sicher (habe leider auch nicht alle Compile-Options in der Hand), aber ich vermute mal, dass es daran liegt und muss mal sehen, ob ich mit -qstrict rumspielen kann...

    Gruß,

    Simon2.



  • Wenn es ein Threading-Problem ist, dann liegt es sicher nicht daran.



  • hustbaer schrieb:

    Wenn es ein Threading-Problem ist, dann liegt es sicher nicht daran.

    Ich bin mir leider nicht sicher, dass es ein Threadingproblem ist....

    Ich habe hier halt nur 2 "Entwicklungsumgebungen":
    - Das "große Projekt"; nur mit Threading, nur sehr begrenzt Einfluß auf Compileschalter und
    - mein "Spielprojekt"; erstmal ohne Threading und dafür den gesamten Build in der Hand.
    Beim ersten habe ich das Problem, beim zweiten nicht.
    Da das Problem auch auftaucht, wenn die Variablen nur const sind, bin ich nicht mehr davon überzeugt, dass es am Threading liegt...
    Ich kann weder beim ersten das Threading "rausmachen" noch die Optimierungsparameter ändern noch beim zweiten mal eben ein analoges Threading einbauen.

    Gruß,

    Simon2.



  • Kannst du beim "grossen" Projekt auch Testläufe machen? Wenn nein, ist das doof, und IMO ein Problem, welches behoben werden sollte. Wenn man nicht das endgültige Programm auf der endgültigen Plattform testen kann, dann bekommt man früher oder später Probleme.

    Wenn schon, dann probier mal folgendes:

    mutex g_m;
    
    void g() {
       static size_t const initVal = f(7ul);
    }
    
    void g_wrapper() {
        lock_mutex(g_m);
        g();
        unlock_mutex(g_m);
    }
    

    Für "mutex", "lock_mutex" und "unlock_mutex" musst du natürlich die entsprechenden Typen/Funktionen der vorhandenen Threading-API einsetzen. Ggf. musst du "mutex" noch irgendwo initialisieren, das machst du dann am besten in main() oder einem Konstruktor einer global instanzierten Klasse (nicht static in einer Funktion!).

    Und dann rufst du nurmehr g_wrapper() statt g() auf.

    Wenn das Problem damit behoben ist, dann ist es ein Threading-Problem. Natürlich ist das keine saubere Lösung, aber zumindest könntest du dann ziemlich sicher sein, dass es daran liegt, dass mehrere Threads gleichzeitig versuchen die Variable zu initialisieren.



  • hustbaer schrieb:

    Kannst du beim "grossen" Projekt auch Testläufe machen? ...

    Ja - das geht glücklicherweise.
    (genaugesagt: Ich kan die natürlich nur auf unserem Integrationssystem machen, aber das ist identisch zu unseren Prodsystem).

    hustbaer schrieb:

    ...Wenn schon, dann probier mal folgendes:...

    Ich bin mir nicht ganz sicher, ob ich es nicht schonmal (erfolglos) mir Serialisierung versucht .. aber ich werde das morgen nochmal angehen.

    Allerdings dachte ich eigentlich, dass ich das hier:

    hustbaer schrieb:

    dass mehrere Threads gleichzeitig versuchen die Variable zu initialisieren.

    schon dadurch ausgeschlossen hätte, dass ich die Variable nur noch const und nicht mehr static) gemacht habe.

    Inzwischen habe ich tatsächlich die Initialisierung in eine Funktion "vorgelagert", die nur noch einmal aufgerufen wird - aber schön finde ich das nicht. Besonders häßlich finde ich, dass ich darauf angewiesen bin, dass eine std::map (die ich auch über eine Funktion initialisiere) nicht verändert wird, weil ich Referenzen auf Elemente weiterreiche. Ideales Feld für ein "const".
    Allerdings traue ich dem Braten nicht ... wenn das mit primitiven Typen nicht klappt, dann fühle ich mich bei einer map auch unwohl.

    Gruß,

    Simon2.



  • Hi hustbaer,

    also auch ein Lock innerhalb der Initfunktion hat keine Änderung gebracht.
    Außerdem:
    - Bislang wurde die init-Funktion sowieso nicht mehrmals aufgerufen (zumindest wenn ich meinen Au(s)g(ab)en trauen kann).
    - Sie liefert auch den richtigen Wert zurück (Einschränkung s.o.)
    - ... erst die Initialisierung der const-Variable schlägt fehl.
    Und wenn da ein Threadingproblem zuschlüge, hätte ich ein eher strukturelles Problem: Wie soll ich das machen? Kann ich in so einer Situation überhaupt noch darauf hoffen, dass der umgebende Code noch regulär aufgerufen wird?

    void g() {
       lock_mutex(g_m);
       size_t const initVal = f();
       unlock_mutex(g_m);
    ...
    

    ... also letztlich glaube ich nicht mehr an ein Threadingproblem, sondern eher an einen übereifrigen Optimierer.

    Nachtrag: In Konstruktoren macht er es wohl richtig.
    Folgendes funktioniert jedenfalls:

    class Wrap() {
       int const parm1;
    public:
       Wrap() : parm1(f()) {}
       int const& p1() const { return parm1; }
    };
    
    void g() {
       static Wrap const w;
       static int const& parm1 = w.parm1();
    

    Gruß,

    Simon2.



  • @Simon2:
    Also lass mich schnell erklären warum ich ursprünglich glaubte dass es ein Threading-Problem ist.
    Wenn du solchen Code hast:

    void foo()
    {
       static int i = bar();
    }
    

    Dann macht der Compiler da meist einfach sowas draus:

    void foo()
    {
       static int i = 0;
       static bool __i_initialized = 0;
       if (__i_initialized == 0)
       {
          __i_initialized = 1;
          i = bar();
       }
    }
    

    Ob __i_initialized jetzt vor oder nach der eigentlichen Zuweisung steht ist auf vielen Plattformen sogar egal (auf Intel x86 grad nicht, aber auf sonst vielen CPUs).
    Ein Problem bekommst du dann, wenn zwei Threads da gleichzeitig reinlaufen.
    Der erste sieht "__i_initialized == 0", und führt die Anweisungen im "if" aus. Setzt also gleich mal "__i_initialized = 1". Der zweite Thread sieht dann "__i_initialized == 1", und springt nichtmehr ins "if", und fängt sofort an "i" zu verwenden. Das aber u.U. noch, bevor der 1. Thread "i" überhaupt geschrieben hat.

    D.h. es kann leicht sein, dass "bar()" hier bloss 1x aufgerufen wird, "bar()" auch den korrekten Wert zurückliefert, aber dennoch ein Thread den falschen Wert für "i" ausliest.

    Dass es bei "nur const" auch passiert, könnte höchstens auf einen sehr aggressiv optimierenden Compiler zurückzuführen sein.

    ----

    Die Sache mit deiner "Wrap" Hilfsklasse ist allerdings seltsam.

    ----

    Wie auch immer, wenn du 100% wasserdicht-thread-sicheren Code haben willst, darfst du keine statics in Funktionen verwenden, die dynamisch initialisiert werden.

    Natürlich kann es sein dass der Optimizer schuld ist. Kannst du denn mal einen Test mit O2 statt O3 machen? Und um welchen Compiler (und Version) handelt es sich denn? GCC? 3.x? 4.x?



  • hustbaer schrieb:

    @Simon2:
    Also lass mich schnell erklären warum ich ursprünglich glaubte dass es ein Threading-Problem ist....

    Das war mir schon klar.
    Mein Hinweis zielte eher darauf ab, dass ich das mit einem "inneren Lock" (der dann auch nichts Anderes machen würde als Dein "__i_initialized") auch nicht beheben würde.

    Da müsste ich einen "äußeren Lock" einbauen, bei dem ich
    - mir einerseits nicht sicher bin, ob das vom Compiler vernünftig umgesetzt wird und
    - andererseits Schwierigkeiten hätte, RAII zum Unlocken zu nutzen.

    void foo()
    {
      {
         myLock lock(myMutex);
         static int const i = bar(); 
      }
      // ... upps, i unbekannt.
    }
    

    hustbaer schrieb:

    ...um welchen Compiler (und Version) handelt es sich denn? GCC? 3.x? 4.x?

    xlC Version 8.0

    Ich kann beim "großen Projekt" leider diese Parameter nicht (mal eben) ändern - und da ich kurz vor Produktivsetzung stehe, möchte ich da wirklich nicht rumfummeln.
    Aber ich werde mal bei meinem kleinen Testzeug mal mit O3 und O2 rumspielen ...

    Gruß,

    Simon2.



  • Sooooooooooooo.

    Da habe ich wohl an der falschen Stelle gesucht und/oder bin einer Seltsamlichkeit des xlC auf die Schliche gekommen.
    Folgendes Testprogramm, das gar nichts mit "Threading" zu tun hat:

    #include <iostream>
    #include <string>
    #include <fstream>
    
    using namespace std;
    
    size_t init_tutnicht(string const&) {
         return 37;
    }
    
    size_t init_tut(const char*) {
         return 38;
    }
    
    int main() {
       static size_t const  val_tut = init_tut("");
       size_t               val = init_tutnicht("");
       size_t const         const_val = init_tutnicht("");
       static size_t        static_val = init_tutnicht("");
       static size_t const  static_const_val = init_tutnicht("");
    
       cerr << "val_tut = " << val_tut << "\n";
       cerr << "val = " << val << "\n";
       cerr << "const_val = " << const_val << "\n";
       cerr << "static_val = " << static_val << "\n";
       cerr << "static_const_val = " << static_const_val << "\n";
    
       return 0;
    }
    

    liefert folgenden Output:

    val_tut = 38
    val = 37
    const_val = 804397936
    static_val = 804397952
    static_const_val = 804397968
    

    Auch in allen anderen Tests (Reihenfolge geändert, init_tut() ohne Parameter, ....) zeigt sich:
    Wenn die init-Funktion einen nicht-POD übernimmt (egal, ob per Kopie, const-Kopie oder const-Referenz und egal, ob als einzigen Parameter oder nicht), funktioniert die anschließende Initialisierung nicht.

    nimmt man statt std::string z.B.:

    struct myType {
       myType(const char* s) {}
       ~myType() { }
    };
    
    size_t init_tutnicht(myType const&) {
         return 37;
    }
    

    erhält man:

    val_tut = 38
    val = 37
    const_val = 0
    static_val = 0
    static_const_val = 0
    

    zwar nicht mehr ganz so auffällig, aber trotzdem falsch.

    ... und schon das Entfernen des Dtors

    struct myType {
       myType(const char* s) {}
    };
    

    bringt schon wieder das gewünschte Ergebnis:

    val_tut = 38
    val = 37
    const_val = 37
    static_val = 37
    static_const_val = 37
    

    Es scheint also irgendwie mit der Konstruktion(sreihenfolge) von Objekten zu tun zu haben ...
    Der Einfluss der Optimierung scheint dabei aber weniger eine Rolle zu spielen.
    Mit allen Optionen -O0 und -O2-5 wird das falsche Ergebnis geliefert ... auch wenn die konkreten "falschen Werte" variieren.

    Gruß,

    Simon2.



  • Naja, das is dann ja keine Seltsamkeit, sondern ein ganz grober Compiler-Fehler.



  • hustbaer schrieb:

    Naja, das is dann ja keine Seltsamkeit, sondern ein ganz grober Compiler-Fehler.

    Das vermute ich auch immer stärker.

    "explizite Konversion" funktioniert übrigens:

    #include <iostream>
    #include <string>
    
    size_t init(std::string const&) {
         return 37;
    }
    
    int main() {
       static size_t const  static_const_val = init(std::string(""));   
       std::cerr << "static_const_val = " << static_const_val << "\n";
       return 0;
    }
    

    =>

    static_const_val = 37
    

    Ergo: Der Compiler hat Schwierigkeiten mit
    - der impliziten Erzeugung (explizit geht)
    - von non-PODs (PODs gehen)
    - für Parameter einer InitFunktion (Ctoren gehen)
    - für consts oder statics verwendet wird.

    Ist vielleicht wirklich eine recht spezielle Situation, aber ich fand die Herangehensweise eigentlich sehr naheliegend...

    Nunja, Zeit, dass IBM sich zu dem Thema äußert.

    Danke für's Mitdenken.

    Gruß,

    Simon2.



  • Jo. Blöd dass ich dich zuerst auf ne falsche Fährte gelockt habe. Naja, kann vorkommen.

    Anderen Compiler nehmen (GCC oder so) ist ja vermutlich keine Option, oder?

    Nunja, Zeit, dass IBM sich zu dem Thema äußert.

    Jo. Poste vielleicht nochmal wenn die nen Kommentar zu dem Ticket (ich vermute du hast eins angelegt) abgegeben haben.



  • hustbaer schrieb:

    Dann macht der Compiler da meist einfach sowas draus: [...]

    Der MSVC-Compiler afaik schon, der GCC laut hier nicht. Ist gerade anscheinend nicht von Belang, soll nur ein kleines "Wissen-Update" sein 🙂



  • hustbaer schrieb:

    Jo. Blöd dass ich dich zuerst auf ne falsche Fährte gelockt habe.

    Macht nix ... auf der war ich ja auch vorher schon ... und so weiß ich jetzt noch ein wenig genauer Bescheid über Probleme konkurriender Zugriffe bei Multithreading (habe hier noch so einen fetten Wälzer, der aber irgendwie nichts für "zwischendurch" oder "Nachttisch" ist.;)

    Anderer Compiler geht bei uns im Hause gar nicht - höchstens aktuellere Version (wobei das auch sehr vel Abstimmungsbedarf erfordert) und natürlich Patches.
    Kann ich aber auch verstehen - schon unserem Projekt alleine war es gar nicht so einfach, eine passende Versionskombination von AIX, iona, dataXtend, AIXLink, DB2, MQSeries, SAP, IBM/CCA, ... herauszubekommen.
    (Anlss war, dass die Versionen von dreien dieser Komponenten aus der Wartung liefen).
    Das Ganze wird halt beliebig komplziert, wenn man noch all die anderen Projekte berücksichtigt, die unsere gemeinsame Software-Entwicklungsumgebung unterstützen und abdecken muss (ganz zu schweigen von den Entwicklungstools selbst). Ist halt ein großer Laden (600 Entwickler - wenn auch die meisten "nur" Java machen ;))...

    Ich kann selbst gar kein Ticket einstellen und warte noch auf denjenigen, der es kann. Vermutlich bekomme ich dann einfach einen Patch oder den Hinweis, es doch dann einfach anders zu machen - scheint so IBM-Standardstrategie zu sein.
    Kann auch gut sein, dass es einen passenden Patch bereits gibt, ich den aber nicht gefunden habe. Ich sage mal Bescheid.

    Danke nochmal für den Beistand,

    Simon2.


Anmelden zum Antworten