Meyers Singleton auf dem Heap



  • hustbaer schrieb:

    Das kann so nicht funktionieren.

    Hoppla, da ist wohl die klammer des ifs verrutscht. So sollte es richtig sein:

    void loop() {
            if (!running.test_and_set()) {
                while (!queue.empty()) {
                    try {
                        AbstractEvent *event = queue.front();
                        if (event) performEvent(event);
                        queue.pop_front();
                    } catch (std::exception e) {
                        std::cerr << e.what() << std::endl;
                    }
                }
                spin.lock();
                std::swap(queue, queueNext);
                spin.unlock();
                running.clear();
            }
        }
    

    hustbaer schrieb:

    Ich vermute was du machen wolltest ist:

    lock
    [...]
    lock
        running = false
    unlock
    

    Ich wollte es schlicht lockfrei machen, der ist auch garnicht nötig, da im Falle running=true die funktion wieder verlassen wird und atomic_flag lockfree ist.
    Folgendes wäre vermutlich deutlicher gewesen:

    if (running.test_and_set()) { 
        return;
    }
    

    hustbaer schrieb:

    In ganz speziellen Fällen mag es trotzdem Sinn machen selbst 'was zu basteln, aber i.A. würde ich dir auf Windows SRW-Locks ans Herz legen. Bzw. wenns plattformübergreifend sein soll einfach mal gucken wie std::mutex sich im Vergleich macht, bevor du beschliesst dass da einfach ein untragbarer Overhead sein muss.

    Ich bin mir den Kosten eines spinlocks durchaus bewusst.
    Mutexe haben in meinem Test recht schlecht abgeschnitten. Simple dargestellt summiert es sich eben auf wenn man mehrere 1000 male Lockt (mit den damit verbunden kosten) statt jeweils nur 3 Pointer zu setzen.

    hustbaer schrieb:

    Bzw. ansonsten guck dich nach einer threadsicheren List-implementierung um. Vielleicht boost::lockfree::queue . (Dass std::list thread safe sein soll ist mir neu. Bzw. ich bin mir sehr sicher dass es nicht so ist.)

    Jo, stimmt, da hatte ich mich wohl verlesen. Aber ich brauche eben grade keine Thread sichere Liste wie ich schon sagte. Inzwischen ist auch auf eine eigene Liste umgestellt (aber noch nicht getestet), das Element wird vorher konstruiert, das event eingefügt und erst nach dem spinlock werden die 3 pointer (eigentlich kommt ja oben noch die Konstruktion und der Pointer fuer das Event obendrauf, aber scheinbar ist auch das schneller) gesetzt.

    Nicht das du jetzt denkst ich nehme deine Tips nicht ernst, ich wollte nur sagen das ich eben auch meine Gründe habe es so zu machen. Generell bin ich für jeden Tip dankbar (ausgenommen jene die darauf hinaus laufen meine frage nicht zu Beantworten) und gegebenefalls werde ich mir die Windows Threading geschichten anschauen.



  • Ich sehe gerade du fügst in die Liste queueNext ein, nicht in queue wie ich angenommen hatte. (Vermutlich, da dein add Code nicht zum Rest passt, da der wohl für nen vector<list<... geschrieben wurde -- und das "Priority" im Namen der Klasse wäre sonst auch etwas unpassend.)

    Ja, in dem Fall, und wenn du den Lock hältst während du in queueNext einfügst, dann sollte das vermutlich funktionieren. Allerdings frage ich mich wieso du erst abarbeitest und dann tauscht - dadurch bist du doch immer einen loop -Aufruf hinten?

    Und... guck dir den Code nochmal an. Was du da geschrieben hast ist nichts anderes als

    Spinlock nextQueueMutex;
        Spinlock loopMutex;
    
        void loop() {
            if (loopMutex.try_lock()) {
                while (!queue.empty()) {
                    try {
                        AbstractEvent *event = queue.front();
                        if (event) performEvent(event);
                        queue.pop_front();
                    } catch (std::exception e) {
                        std::cerr << e.what() << std::endl;
                    }
                }
                nextQueueMutex.lock();
                std::swap(queue, queueNext);
                nextQueueMutex.unlock();
                loopMutex.unlock();
            }
        }
    

    Damit "siehst" du was du hier hast, nämlich nichts anderes als eine "Try-Mutex" (dass diese als Spinlock implementiert ist spielt ja keine Rolle wenn es um das Konzept "Mutex" geht). Und jetzt sieht man auch schön dass hier nichts wirklich lock-free ist.

    Als nächstes... weder deine loop -Funktion noch deine add -Funktion sind Exception-safe.
    Bei loop gibt es zwei Probleme:

    1. Wenn etwas anderes als std::exception fliegt bist du tot.
    2. Wenn performEvent bei einem bestimmten Event immer eine Exception wirft bist du ebenso tot. Dann versucht die Schleife nämlich immer wieder und wieder den selben Event zu bearbeiten, was immer wieder und wieder eine Exception wirft, wodurch der Event dann nie aus der Queue genommen wird. (Was das if (event) da macht ist mir auch nicht ganz klar - null Pointer muss man doch gar nicht erst einfügen?)

    Und was add angeht...
    std::list besorgt in push_back dynamisch Speicher. Das kann schief gehen. Der Fall ist zugegebenermassen selten und ein (nicht-triviales) Programm das an allen möglichen Stellen mit bad_alloc klarkommt ist verflixt schwer bis unmöglich zu schreiben... aber wenn es einem schon so ins Gesicht springt wie hier, dann sollte man den Fall behandeln. Einfachste Lösung: noexcept dranschreiben, dann bricht der Compiler im Falle des Falles das Programm einfach ab.

    Bessere Lösung: std::unique_lock (für das try_lock in loop ) bzw. std::lock_guard (für den Rest) verwenden.

    Aber nochmal zurück zu " std::list besorgt in push_back dynamisch Speicher". Das heisst automatisch auch dass du den Lock für wesentlich länger als "drei Zeiger vertauschen" hältst, nämlich für so lange wie halt der Allocator braucht um ne Neue Node zu besorgen. Es heisst auch dass du Performance verschenkst. Lösung: verwende eine "intrusive" Datenstruktur (siehe z.B. Boost.Intrusive). So kannst du den Speicher besorgen bevor du die "list mutex" lockst, das Einfügen beschränkt sich dann wirklich auch ein paar einfache Operationen mit Zeigern.

    Und nochmal zum Thema Performance: wenn es dir um Performance geht, dann solltest du nicht die Default-Argumente von test_and_set bzw. clear verwenden, denn mit denen bekommst du unnötig schlechte Performance. Da es hier um ne Try-Mutex geht sollte für lock/try_lock die order std::memory_order_acquire und für unlock die order std::memory_order_release ausreichend sein. Mehr garantieren schliesslich die Standard-Mutexen auch nicht (und auch die meisten Thirdparty Mutexen nicht). Zumindest auf x86 macht das nen Unterschied, denn bei x86 ist clear(std::memory_order_release) ein einfaches mov , clear (mit Default-Argument std::memory_order_seq_cst ) ist dagegen ein wesentlich teureres xchg .
    (Es mag sein dass manche Compiler zu doof sind clear(std::memory_order_release) zu einem einfachen mov zu optimieren, aber grundsätzlich ist es möglich, und zumindest aktuelle GCC Versionen sind schlau genug.)

    ps

    Mutexe haben in meinem Test recht schlecht abgeschnitten.

    Auf welcher Plattform (OS, Compiler) und mit welcher Mutex-Klasse hast du getestet? Und was bedeutet "recht schlecht"?



  • seelenquell schrieb:

    Die frage ist jetzt ob ich beim Meyers Singleton die statische Variable auch als Pointer deklarieren kann (sprich den speicher auf dem Heap reserviern kann)
    ...

    Angenommen, Deine großen collections sind keine array in der Klasse, sondern ganz normale C++ container, dann alloziieren die Ihren Speicher immer vom heap, auch wenn sie selbst statisch alloziiert sind.

    Ansonsten wäre es aber auch thread save.

    Und Warnung: Singletons sind in der Regel ein Designfehler.



  • hustbaer schrieb:

    Ich sehe gerade du fügst in die Liste queueNext ein, nicht in queue wie ich angenommen hatte. (Vermutlich, da dein add Code nicht zum Rest passt, da der wohl für nen vector<list<... geschrieben wurde -- und das "Priority" im Namen der Klasse wäre sonst auch etwas unpassend.)

    Jup, da hast du recht, eigentlich wars ein std::array<List ..., 16>. Die 2 listen sind einfach dafür da, das sie beim einfügen und löschen nicht aufeinander laufen.

    hustbaer schrieb:

    Ja, in dem Fall, und wenn du den Lock hältst während du in queueNext einfügst, dann sollte das vermutlich funktionieren. Allerdings frage ich mich wieso du erst abarbeitest und dann tauscht - dadurch bist du doch immer einen loop -Aufruf hinten?

    Da ich überlegt hatte eine externe Unterbrechung der Schleife vorzusehen (z.B. vom Global Event Manager) falls ich die Rechenleistung kurzfristig anderweitig brauche (z.B. für Grafikberechnungen). Würde ich jetzt die Schleife verlassen wenn noch nicht alle events abgearbeitet sind, würde beim nächsten Aufruf getauscht und nicht die events die zuerst anstehen abgearbeitet, sondern die aus dem anderen Container die eigentlich erst hinterher dran sind. Ich fand das so am übersichtlichsten, dann brauche ich nicht am Anfang noch nen if vor das tauschen reinwurschsteln (vermutlich brauch ichs eh, aber das kommt noch).

    hustbaer schrieb:

    Damit "siehst" du was du hier hast, nämlich nichts anderes als eine "Try-Mutex" (dass diese als Spinlock implementiert ist spielt ja keine Rolle wenn es um das Konzept "Mutex" geht). Und jetzt sieht man auch schön dass hier nichts wirklich lock-free ist.

    Technisch gesehen ist vermutlich nichts wirklich lockfrei. Aber versteh bitte das ich keine "richtigen" Mutexe/Locks einsetzen will, das will ich unter allen Umstaenden vermeiden. Mir gehts dabei ums Prinzip, ich will darauf hinaus am Ende keine "sichtbaren" Locks zu haben. Ich hatte vor Jahren mal zimliche schwierigkeiten mit Locks, vermutlich habe ich desswegen so lange Clojure programmiert, wo es sprachlich garkeine Möglichkeit gibt Locks zu setzen, das war eine wirkliche Offenbarung. Daher kenne ich auch die Konzepte von Atomic, STM und async recht gut, in C++ muss ich mich da allerdings nochmal richtig durchwühlen (wie bei vielen anderen dingen auch).

    hustbaer schrieb:

    Als nächstes... weder deine loop -Funktion noch deine add -Funktion sind Exception-safe.
    Bei loop gibt es zwei Probleme:

    1. Wenn etwas anderes als std::exception fliegt bist du tot.
    2. Wenn performEvent bei einem bestimmten Event immer eine Exception wirft bist du ebenso tot. Dann versucht die Schleife nämlich immer wieder und wieder den selben Event zu bearbeiten, was immer wieder und wieder eine Exception wirft, wodurch der Event dann nie aus der Queue genommen wird.

    Das ist ein guter Punkt, in exceptions muss ich mich noch richtig einarbeiten, da hab ich noch zimliche defizite. Das mit der Schleife stimmt natuerlich und wird angepasst.

    hustbaer schrieb:

    (Was das if (event) da macht ist mir auch nicht ganz klar - null Pointer muss man doch gar nicht erst einfügen?)

    Jo, das ist in dem Beispiel natürlich unsinnig und ein Überbleibsel aus dem ursprünglichen Code. Da wird in der Liste nur ein Verweis auf das Event gespeichert, welches in einer anderen Collection gespeichert ist. Bei Realmübergreifenden events kanns sein das dieses schon abgearbeitet ist und die Collection null zurueck gibt. Das das nicht Performant ist, ist klar, allerdings habe ich da schon die ein oder andere Idee.

    hustbaer schrieb:

    Aber nochmal zurück zu " std::list besorgt in push_back dynamisch Speicher". Das heisst automatisch auch dass du den Lock für wesentlich länger als "drei Zeiger vertauschen" hältst, nämlich für so lange wie halt der Allocator braucht um ne Neue Node zu besorgen. Es heisst auch dass du Performance verschenkst. Lösung: verwende eine "intrusive" Datenstruktur (siehe z.B. Boost.Intrusive). So kannst du den Speicher besorgen bevor du die "list mutex" lockst, das Einfügen beschränkt sich dann wirklich auch ein paar einfache Operationen mit Zeigern.

    Jup, hatte ich ja schon geschrieben. Meine List Implementierung hab ich mal auf pastebin gestellt: http://pastebin.com/ZM6Q2eig
    Ist allerdings alles andere als fertig und ich kanns grad nicht testen. Vermutlich werd ich die dann auch in einem Atomic speichern (afaik erfüllt sie die Vorrausetungen dafür).

    Boost finde ich zwar Klasse, dennoch möchte ich für triviale fälle vermeiden da Boost einfach Riesig ist und meine lib nicht davon abhängig sein soll (ich verwende aktuell zwar property_tree, das lässt sich aber per #ifndef SECS_NOBOOST abschalten).

    hustbaer schrieb:

    Und nochmal zum Thema Performance: wenn es dir um Performance geht, dann solltest du nicht die Default-Argumente von test_and_set bzw. clear verwenden, denn mit denen bekommst du unnötig schlechte Performance. Da es hier um ne Try-Mutex geht sollte für lock/try_lock die order std::memory_order_acquire und für unlock die order std::memory_order_release ausreichend sein. Mehr garantieren schliesslich die Standard-Mutexen auch nicht (und auch die meisten Thirdparty Mutexen nicht). Zumindest auf x86 macht das nen Unterschied, denn bei x86 ist clear(std::memory_order_release) ein einfaches mov , clear (mit Default-Argument std::memory_order_seq_cst ) ist dagegen ein wesentlich teureres xchg .
    (Es mag sein dass manche Compiler zu doof sind clear(std::memory_order_release) zu einem einfachen mov zu optimieren, aber grundsätzlich ist es möglich, und zumindest aktuelle GCC Versionen sind schlau genug.)

    Sehr intressant, danke. Die semantiken kenne ich zwar, aber einen genaueren einblick hatte ich da natürlich nicht. Zu dem Zeitpunkt als ichs geschrieben hatte sollte es zunächst einfach nur funktionieren und möglichst simpel sein (ich habe damit ne andere Collection ersetzt). Anpacken muss ichs sowieso nochmal, da werd ich das beherzigen.

    hustbaer schrieb:

    ps

    Mutexe haben in meinem Test recht schlecht abgeschnitten.

    Auf welcher Plattform (OS, Compiler) und mit welcher Mutex-Klasse hast du getestet? Und was bedeutet "recht schlecht"?

    [/quote]
    Schon eine weile her, aber ich meine es wäre um den Faktor 4 rum gewesen. Getestet habe ich auf Windows mit dem 64bit mingw-builds-win-sjlj (mit der Implementation des Singletons habe ich dann auf mingw-builds-posix-sjlj umgestellt da die win variante mit std::call_once etc nicht klarkommt). Zum habe ich glaube ich std::lock_guard verwendet.
    Letzten Endes ists aber egal, da ich mich schon dafür entschlossen habe keine Mutexe zu verwenden und Locks konsequent zu vermeiden/rauszuschmeissen sofern irgenwie möglich. In der Beziehung lass ich auch nicht mit mir reden.

    Torsten Robitzki schrieb:

    Angenommen, Deine großen collections sind keine array in der Klasse, sondern ganz normale C++ container, dann alloziieren die Ihren Speicher immer vom heap, auch wenn sie selbst statisch alloziiert sind.

    Ansonsten wäre es aber auch thread save.

    Danke für die Antwort, irgendwer meinte zwar etwas Ähnliches, da war ich mir aber nicht sicher ob er auch explizit Pointer meinte.

    Torsten Robitzki schrieb:

    Und Warnung: Singletons sind in der Regel ein Designfehler.

    Ehrlichgesagt kann ich das mittlerweile nicht mehr glauben. Ich habe das Argument schon unendlich oft gehört aber nie eine richtige Begründung der Aussage. Klar sind sie im Prinzip globale Variablen, im Gegensatz dazu aber gekampselt, was insgesammt auch nur ein Problem ist wenn ich von überall im Code darauf referenziere. Tue ich das nicht, sondern übergebe ich sie an den stellen in denen ich sie brauche habe ich kein Problem damit. Dagegen ist es viel Schwerwiegender wenn eine Resource die nur ein einzelnes mal vorkommen darf mehrmals vorkommt (oder kann).



  • seelenquell schrieb:

    Ehrlichgesagt kann ich das mittlerweile nicht mehr glauben. Ich habe das Argument schon unendlich oft gehört aber nie eine richtige Begründung der Aussage. Klar sind sie im Prinzip globale Variablen, im Gegensatz dazu aber gekampselt, was insgesammt auch nur ein Problem ist wenn ich von überall im Code darauf referenziere. Tue ich das nicht, sondern übergebe ich sie an den stellen in denen ich sie brauche habe ich kein Problem damit. Dagegen ist es viel Schwerwiegender wenn eine Resource die nur ein einzelnes mal vorkommen darf mehrmals vorkommt (oder kann).

    Es gibt in C++ ein banales Sprachmittel, um auszudrücken, dass eine Ressource einmal vorkommt: Parameterübergabe.

    void f(resource &singleton)
    {
      //...
    }
    
    struct user
    {
      explicit user(resource &singleton);
    };
    

    Ta-da! Problem gelöst. Wenn man keine Singletons verwendet, stellt sich in f und in den user -Methoden gar nicht die Frage nach weiteren Instanzen von resource .

    Es geht bei Singletons nicht darum, 1-zu-N-Beziehungen zu modellieren oder irgendwie die Korrektheit zu fördern. Hört doch auf das zu behaupten. Es geht um erstens Faulheit und zweitens Unfähigkeit saubere Beziehungen zwischen Komponenten zu finden. Man muss sich überwinden das mal vernünftig zu lernen. Irgendwann macht es dann *klick* und man hat keine Angst mehr vor Klassen und Referenzen.
    Ich bin sehr an Beispielen für sinnvolle Verwendung von Singletons interessiert. Habe noch nie eins gesehen.

    Wenn ich deinen Code lese, sehe ich da ungefähr einen io_service :

    class EventPriorityArray
    {
    	boost::asio::io_service m_queue;
    
    public:
        void add(AbstractEvent *event) {
    		m_queue.post([event] { event->run(); });
        }
    
        void loop() {
    		m_queue.poll();
        }
    };
    

    Der hat nur eine Queue und nicht zwei wie du. Dein Ansatz mit loop ist nämlich seltsam. Warum machst du das so? Mit fehlt jetzt noch das ganze Bild. Wer ruft loop auf?

    Verkettete Listen sind für Performance übrigens das schlechteste, was man nehmen kann.



  • Nabend :-),

    seelenquell schrieb:

    Ehrlichgesagt kann ich das mittlerweile nicht mehr glauben. Ich habe das Argument schon unendlich oft gehört aber nie eine richtige Begründung der Aussage.

    Ich habe das in dem Satz ja nur postuliert, ein Argument hast Du von mir noch überhaupt nicht gehört 😉

    seelenquell schrieb:

    Klar sind sie im Prinzip globale Variablen, im Gegensatz dazu aber gekampselt, was insgesammt auch nur ein Problem ist wenn ich von überall im Code darauf referenziere.

    Ich habe überhaupt nichts gegen globale Variablen, solange sie ein guter Weg sind, das gestellte Problem zu lösen. std::cout, std::clog, std::cerr sind übrigens alles globale Variablen.

    Mein Problem mit singletons ist, dass sie überhaupt kein Problem lösen. Das ursprüngliche Ziel war, sicher zu stellen, dass es exakt nur eine Instanz einer Klasse in einem Programm gibt. Aber warum sollte ich das wollen? Welche Anforderung kann ich damit erfüllen bzw. welches Design Ziel erreichen?

    Kapselung, wie Du es schreibst, ist kein Design-Ziel. Lose Koppelung kann man durch Kapselung erreichen, da dadurch die Schnittstelle zu einer Komponente schmaler wird. Singletons führen aber zu allem anderen, aber nicht zu lose gekoppelten Komponenten. Du kannst ja mal versuchen, aus einem System mit vielen Singletons irgend etwas wieder zu verwenden. Meistens ziehst Du da an einem Ende und über die Singletons, hast Du dann das ganze System in der Hand.

    In C++ setzt man singletons meist gar nicht für das ursprünglich Ziel ein, sondern um Probleme mit Initialisierungsreihenfolgen zu lösen. Dagegen spricht auch nichts, das sind dann Implementierungsdetails und so etwas möchte ich nicht als Schnittstelle bedienen müssen.

    log::instance()->log_debug( "Foo" );
    // vs.
    log::log_debug( "Foo" );
    

    da interessiert es mich doch nicht, dass die logging Komponente intern als Singleton implementiert ist, damit ich zu jedem Zeitpunkt darauf zugreifen kann.

    mfg Torsten

    P.S.: Anekdoten:

    https://groups.google.com/d/topic/de.comp.objekt/i4Ana3y_tGc/discussion
    https://www.xing.com/communities/posts/singletons-pro-strich-con-1004722838



  • TyRoXx schrieb:

    Es gibt in C++ ein banales Sprachmittel, um auszudrücken, dass eine Ressource einmal vorkommt: Parameterübergabe.
    [...]
    Ta-da! Problem gelöst. Wenn man keine Singletons verwendet, stellt sich in f und in den user -Methoden gar nicht die Frage nach weiteren Instanzen von resource .

    Ich stimme dir da voll zu das die Übergabe als Parameter wohl am Sinnvollsten ist, wie ich schonmal geschrieben hatte, und was auch Torsten Robitzki (siehe Unten) angesprochen hat gings mir dabei hauptsächlich initialisierungsprobleme zu lösen. Ich werde das Singleton für den Global Event Manager vermutlich auch als hauptsächlich als interne statische Variable (oder Klasse) der normalen Eventmanager Klasse verwenden, damit jeder Eventmanager bestimmte Informationen an den Globalen Eventmanager weiterleiten kann (z.b. registrieren sich diese dort). Der Global Event Manager erbt auch nicht von der Eventmanager KLasse, er steuert diese aber. Wie das am Ende genau aussieht wird sich noch zeigen.

    TyRoXx schrieb:

    Es geht bei Singletons nicht darum, 1-zu-N-Beziehungen zu modellieren oder irgendwie die Korrektheit zu fördern. Hört doch auf das zu behaupten. Es geht um erstens Faulheit und zweitens Unfähigkeit saubere Beziehungen zwischen Komponenten zu finden. Man muss sich überwinden das mal vernünftig zu lernen. Irgendwann macht es dann *klick* und man hat keine Angst mehr vor Klassen und Referenzen.
    Ich bin sehr an Beispielen für sinnvolle Verwendung von Singletons interessiert. Habe noch nie eins gesehen.

    Naja, was die Initialisierung angeht finde ich schon das es die Korrektheit fördern kann, zumindest erleichters es erheblich zur rechten Zeit das voll initialisierte Objekt zur Hand zu haben, sicher gibts da andere Wege aber ich finde Singletons richtig eingesetzt immer noch nicht übel. Das es was mit Faulheit zu tun hat den einfacheren Ansatz zu wählen, da ist sicher was wahres dran. Ein einfaches Design kann aber auch Vorteile haben, unoetige Komplexität ist häufig eine Fehlerquelle. Ob es was mit Unfähigkeit zu tun hat wird sich in meinem Fall noch zeigen. Falls du einen Vorschlag hast wie ich meine 1:N Beziehungen, inclusive ordentlicher Initialisierung möglichst ausserhalb der Main hinbekommen werde ich mir den gerne anschauen.

    TyRoXx schrieb:

    Wenn ich deinen Code lese, sehe ich da ungefähr einen io_service :
    [...]
    Der hat nur eine Queue und nicht zwei wie du. Dein Ansatz mit loop ist nämlich seltsam. Warum machst du das so? Mit fehlt jetzt noch das ganze Bild. Wer ruft loop auf?

    boost::asio werd ich mir mal anschauen.
    Die 2 Querys habe ich desswegen damit in die eine ungestört eingefügt, und aus der anderen rausgeholt werden kann. So beinflussen sich die beiden Funktionen möglichst wenig. Sonst müsste ich entweder die Liste jedes mal komplett sperren oder ich muss aufwendig checken das nicht beide Funktionen auf das gleiche Element zugreifen. Sicher gibts noch Threadsichere alternativen, aber da es speicher- und performancetechnisch relativ egal ist ob ich eine oder 2 Listen verwende habe ich mich für diese einfache Alternative entschieden.

    Loop wird im übrigen in der loop in der main funktion (meiner TestApp) aufgerufen, später soll das der Global Event Manager erledigen, dessen loop dann in der main zyklisch neu gestartet wird (ich meine mal gelesen zu haben das die Hauptschleife möglichst immer in der main sein sollte).

    TyRoXx schrieb:

    Verkettete Listen sind für Performance übrigens das schlechteste, was man nehmen kann.

    Ja das stimmt leider, ich bin noch am überlegen was ich am besten mache. Sicherlich wäre eine Array-Queue (ich hab die auch schon als Listen oder Trees gesehen) in der richtigen Größe das beste. Ich hoffe insgeheim aber noch das ich der Zeit in der auf die Daten vom Heap geholt werden (beim Zugriff auf das listenelement) was anderes tun kann, Erlang ist bei sowas extrem gut das es Prozesse extrem schnell switchen kann.

    Torsten Robitzki schrieb:

    Ich habe überhaupt nichts gegen globale Variablen, solange sie ein guter Weg sind, das gestellte Problem zu lösen. std::cout, std::clog, std::cerr sind übrigens alles globale Variablen.

    Mein Problem mit singletons ist, dass sie überhaupt kein Problem lösen. Das ursprüngliche Ziel war, sicher zu stellen, dass es exakt nur eine Instanz einer Klasse in einem Programm gibt. Aber warum sollte ich das wollen? Welche Anforderung kann ich damit erfüllen bzw. welches Design Ziel erreichen?

    Kapselung, wie Du es schreibst, ist kein Design-Ziel. Lose Koppelung kann man durch Kapselung erreichen, da dadurch die Schnittstelle zu einer Komponente schmaler wird. Singletons führen aber zu allem anderen, aber nicht zu lose gekoppelten Komponenten. Du kannst ja mal versuchen, aus einem System mit vielen Singletons irgend etwas wieder zu verwenden. Meistens ziehst Du da an einem Ende und über die Singletons, hast Du dann das ganze System in der Hand.

    In C++ setzt man singletons meist gar nicht für das ursprünglich Ziel ein, sondern um Probleme mit Initialisierungsreihenfolgen zu lösen. Dagegen spricht auch nichts, das sind dann Implementierungsdetails und so etwas möchte ich nicht als Schnittstelle bedienen müssen.

    Genau dafuer ist es auch gedacht. Das mit den lose gekoppelten Komponenten ist ein guter Punkt. Mehr oder weniger meinte ich das mit "überall referenzieren", problematisch ist das aber auch mit Globalen variablen (cout aus nem Projekt zu schmeissen das auf ausgabe spezialisiert ist, ist bestimmt auch interessant). Das es nur eine Instanz geben soll liegt eben daran Begründet das der Global Event Manager alles andere verwalten soll, ob es letztenende notwendig ist das es nur eine Instanz geben kann wird sich zeigen, momentan ists die einfachste Lösung.

    Kapselung ist da sicher nicht ganz der richtige, aber verwende Begriffe wie Kapselung, Schnittstelle und lose Kopplung oft synonym, was sicher nicht ganz richtig ist aber ich finde mehr oder weniger laeufts auf das Gleiche hinaus (Lose Kopplung ereiche ich über ne gute Schnittstellendefinition und das sich die Leute dran halten über Kapselung). Ich bin auch nicht ganz so fit was "normale" Softwareentwicklung angeht, ist auch einer der Gründe warum ich das meiste selbst machen will, aus Fehlern lernt man schliesslich.

    Torsten Robitzki schrieb:

    log::instance()->log_debug( "Foo" );
    // vs.
    log::log_debug( "Foo" );
    

    da interessiert es mich doch nicht, dass die logging Komponente intern als Singleton implementiert ist, damit ich zu jedem Zeitpunkt darauf zugreifen kann.

    Jup, ob ich überhaupt ne getInstance methode brauche werde ich sehen, momentan siehts eher nicht danach aus, aber ursprünglich hat der GEM von normalen Eventmanager geerbt, da war es natürlich einfacher die Methoden über instance aufzurufen als für alles eine statische Methode zu schreiben und weiterzuleiten. Mitlerweise ist er als statische Variable in die eventmanager Klasse hinein gewandert, da Größtenteils nur diese ihn verwenden, möglicherweise wandert er als private Klass auch ganz rein.

    Torsten Robitzki schrieb:

    P.S.: Anekdoten:

    https://groups.google.com/d/topic/de.comp.objekt/i4Ana3y_tGc/discussion
    https://www.xing.com/communities/posts/singletons-pro-strich-con-1004722838

    Ich werd mal reinschauen 😉

    Dank eurer Argumente für das Für und Wider von Singletons bin ich jetzt schonmal schlauer, bestärkt mich aber auch darin das Singletons in meinem Fall nicht unbedingt verkehrt sind, ob ich sie langfristig noch brauche wird sich zeigen.

    Ich hoffe ich hab nicht zu viel Mist geschrieben, ist schon spät 😉

    Grüße



  • seelenquell schrieb:

    Ich werde das Singleton für den Global Event Manager vermutlich auch als hauptsächlich als interne statische Variable (oder Klasse) der normalen Eventmanager Klasse verwenden, damit jeder Eventmanager bestimmte Informationen an den Globalen Eventmanager weiterleiten kann (z.b. registrieren sich diese dort).

    Sowas haben wir sicherlich auch öfter mal in unserer Software... Nur stört es immer, wenn man dann plötzlich mehreren Instanzen von dem ganzen System haben will. Das kann man im Voraus alles schlecht abschätzen. Irgendwann wird dein System (nochmal) viel größer, und dann gibts lauter neue Entwickler und Anforderungen, und jemand sagt, ja, dieses Event Zeugs ist cool, will ich auch verwenden, aber mein Sub System hat nichts mit dem Rest zu tun und der globale Event Manager soll davon gar nichts mitbekommen.



  • seelenquell schrieb:

    Technisch gesehen ist vermutlich nichts wirklich lockfrei.

    Doch, lock-freie Datenstrukturen. Dinge wie try_lock bei einer Try-Mutex sind auch lock-free.

    seelenquell schrieb:

    Aber versteh bitte das ich keine "richtigen" Mutexe/Locks einsetzen will, das will ich unter allen Umstaenden vermeiden. Mir gehts dabei ums Prinzip, ich will darauf hinaus am Ende keine "sichtbaren" Locks zu haben. Ich hatte vor Jahren mal zimliche schwierigkeiten mit Locks, vermutlich habe ich desswegen so lange Clojure programmiert, wo es sprachlich garkeine Möglichkeit gibt Locks zu setzen, das war eine wirkliche Offenbarung. Daher kenne ich auch die Konzepte von Atomic, STM und async recht gut, in C++ muss ich mich da allerdings nochmal richtig durchwühlen (wie bei vielen anderen dingen auch).

    Sorry, aber das klingt alles nicht so als ob du genügend Grundlagen/Vorkenntnisse hättest um sowas erfolgreich auf die Beine zu stellen.



  • Ist eine Weile her, aber ich hatte viel tun (nicht nur Programmieren).

    Mechanics schrieb:

    seelenquell schrieb:

    Ich werde das Singleton für den Global Event Manager vermutlich auch als hauptsächlich als interne statische Variable (oder Klasse) der normalen Eventmanager Klasse verwenden, damit jeder Eventmanager bestimmte Informationen an den Globalen Eventmanager weiterleiten kann (z.b. registrieren sich diese dort).

    Sowas haben wir sicherlich auch öfter mal in unserer Software... Nur stört es immer, wenn man dann plötzlich mehreren Instanzen von dem ganzen System haben will. Das kann man im Voraus alles schlecht abschätzen. Irgendwann wird dein System (nochmal) viel größer, und dann gibts lauter neue Entwickler und Anforderungen, und jemand sagt, ja, dieses Event Zeugs ist cool, will ich auch verwenden, aber mein Sub System hat nichts mit dem Rest zu tun und der globale Event Manager soll davon gar nichts mitbekommen.

    Jup, könnte Aufwändig werden das im nachhinein auseinander zu wurschteln.
    Das dachte ich mir letztenendes auch und hab einiges umgestellt. Den GlobalEventManager selbst ist nun kein Singleton mehr (und heisst nun EventServer), die Konstruktoren sind protected. Mein Singleton EventServer der intern verwendet wird erbt einfach von der KLasse.

    Das lässt sich per #define ECS3P_CUSTOM_EVENTSERVER CustomClass, auch einstellen. Der Rest ist eigentlich per Template Parameter konfigurierbar aber das ganze sah beim debuggen relativ unschön aus (bei typedef SystemManger<EventManagerBase, EventServer> EventManger; wobei der EventServer auch wieder ein Template ist wird leider immer der volle name incl aller paramter ausgegeben).

    Deshalb erben EventManager und co die einzelnen Template Klassen einfach (aka class Eventmanager : public SystemManager<EventManager, EventServer_t>), EventServer_t wird dann einfach in einem #ifdef block als typedef gesetzt. Ob das so ideal ist kann ich nicht sagen, allerdings wars für die simpelste Lösung das von Benutzer konfigurierbar zu machen. Auf dem gleiche weg lassen sich auch Systeme komplett deaktivieren.

    hustbaer schrieb:

    seelenquell schrieb:

    Technisch gesehen ist vermutlich nichts wirklich lockfrei.

    Doch, lock-freie Datenstrukturen. Dinge wie try_lock bei einer Try-Mutex sind auch lock-free.

    Muss ich mir mal anschauen, eigentlich waere mir STM ja am liebsten, aber bis 2017 werde ich da vermutlich noch mindestens warten müssen wenn ich keine externe lib verwenden will. Je nachdem wie lange es dauert bis die Compiler es unterstützen eher noch länger.

    hustbaer schrieb:

    seelenquell schrieb:

    Aber versteh bitte das ich keine "richtigen" Mutexe/Locks einsetzen will, das will ich unter allen Umstaenden vermeiden. Mir gehts dabei ums Prinzip, ich will darauf hinaus am Ende keine "sichtbaren" Locks zu haben. Ich hatte vor Jahren mal zimliche schwierigkeiten mit Locks, vermutlich habe ich desswegen so lange Clojure programmiert, wo es sprachlich garkeine Möglichkeit gibt Locks zu setzen, das war eine wirkliche Offenbarung. Daher kenne ich auch die Konzepte von Atomic, STM und async recht gut, in C++ muss ich mich da allerdings nochmal richtig durchwühlen (wie bei vielen anderen dingen auch).

    Sorry, aber das klingt alles nicht so als ob du genügend Grundlagen/Vorkenntnisse hättest um sowas erfolgreich auf die Beine zu stellen.

    Das Projekt ist ja auch sowas wie mein "mal schauen was ich hinkrieg". Wenns nicht nach meinen Vorstellungen machbar ist muss ich eben auf ein anderes Konzept zurückgreifen. Ich hab aber nunmal mit Locks in Java (ich kenne eigentlich bisher Mutex und Lock nur synonym) relativ schlechte Erfahrungen gemacht. Nachdem ich mit Java angefangen hatte hab ich mich ne Weile mit nem Websever Projekt von nem Bekannten beschäftigt, das ist quasi ständig in nen Deadlock gerannt, irgendwann hat dann der Bekannte auch aufgehört und nochmal von vorne angefangen (da hatte ich schon lange keinen Nerf mehr). Clojure, das ja auch auf der JVM läuft und mit Java voll kompatibel ist, hat die Probleme nicht. Daten sind per se Immutable, es gibt standartmäßig keine Variablen, ändere ich eine Datenstruktur wird eine Kopie erzeugt. Um Variable Werte speichern zu können muss ich da auf Atomics, Agents oder Refs (transactional references) zurückgreifen, welche alle Thread safe sind und nicht blockieren. Clojure wurde auch mit dem Ziel entwickelt weshalbs auch relativ flott ist. Im vergleich zu anderen HTTP Servern ist der code von Clojure-Ring wohl desshalb auch zimlich einfach gehalten.

    Sorry fuer den langen Text, aber ich wollte nur sagen das ich mit Concurrency in anderen Sprachen schon einiges zu tun hatte. Ob und wie sich das auf C++ übertragen lässt wird sich zeigen. Da die Benchmarks zum Thema Nebenläufigkeit in C++ die ich kenne (vor allem auf http://www.grimm-jaud.de) zimlich ernüchternd ausfallen, habe ich beschlossen innerhalb meiner Systeme weitgehend darauf zu verzichten, es hat immer nur ein Thread gleichzeitig Zugriff auf einen Verbund von Systemen (das steuert der Eventmanager), eine Ausnahme ist atm nur die Eventqueue in die von aussen gepusht werden kann, und das Positioning System, dessen variabler Teil vom Eventmanager beschrieben wird.

    Vielleicht kennt ja zufällig jemand nen guten link zum Thema multithreading Performance, habe leider bisher wenig gefunden, http://www.grimm-jaud.de ist da leider auch nicht besonders aussagekräftig.


Anmelden zum Antworten