std::list und MultiThread...



  • Hallo zusammen,

    ich weiss das die list eigentlich nicht Threadsafe ist, sprich man muss sie synchronisieren.
    Aber wenn ich folgende Situation habe:

    Thread A schreibt nur neue Objekte in die std::list (mit push_back())
    Thread B liest und löscht immer das erste Objekt (.empty(), front(), pop_front())

    Wenn ich noch kontrolliere das die max Grösse der Liste nicht überschritten wird, müsste dies doch eigentlich schon synchron sein...
    Oder hab ich da was übersehen?

    Vielen Dank und Gruss



  • Da du nicht weißt, wie die Listen intern konzipiert sind, kann man keine Angaben darüber machen, in welchem Umfang die Daten umstrukturiert werden, wenn ein Datensatz gelöscht wird. Wenn du also sauber programmieren willst, dann bleibt dir nichts anderes übrig, als die Operationen korrekt zu synchronisieren.

    Insgesamt ist das in C++ aber auch keine große Sache.

    class MutexLocker
    {
     private:
      Mutex mutex;
    
     public:
      MutexLocker()
      { mutex.lock(); }
    
      ~MutexLocker()
      { mutex.unlock(); }
    };
    

    Verwendung:

    {
       MutexLocker lock;
       // [...]
    } // Mutex wird automatisch wieder freigegeben
    


  • Hallo,

    ja genau das hoffte ich eigentlich in Erfahrung zu kriegen...

    Meine Anwendung:
    Thread A empfängt Packete auf einem Socket
    Thread B holt diese immer ab

    Wenn ich das ganze synchronisieren muss, müsste ich eben noch die Liste immer kopieren und das hoffte ich zu umgehen 😉

    Gruss



  • Du kannst doch einfach eine eigene Klasse um die std::list basteln.

    class MyList
    {
     private:
      std::list<...> list;
    
     public:
      void add(x)
      {
       MutexLocker lock;
       list.push(x);
      }
    
      void remove(x)
      {
       MutexLocker lock;
       list.erase(x);
      }
    };
    

    Ich bin mir nur nicht sicher, ob man diese Klasse als Referenz an andere Threads übergeben kann. Aber eigentlich müsste das so gehen...



  • Guest_111 schrieb:

    Oder hab ich da was übersehen?

    Ja. push_back erzeugt nicht nur einen neuen Listenknoten, sondern verändert auch den letzten Listenknoten (falls vorhanden), nämlich dessen next-Pointer.
    pop_front löscht nicht nur den ersten Listenknoten sondern modifiziert auch noch den zweiten (der zum ersten wird, falls vorhanden), nämlich dessen prev-Pointer.

    Wenn bei einer Liste mit zwei Elementen gleichzeitig push_back und pop_front ausführst, wird also gleichzeitig das zweite Element bearbeitet, wenngleich es unterschiedliche Member des Knotens sind. Klingt irgendwie trotzdem potentiell ungesund.
    Wenn du bei einer Liste mit nur einem Element gleichzeitig push_back und pop_front machst gibts Chaos: der neu hinten angefügte Listenknoten könnte einen prev-Pointer erhalten, der auf den im selben Moment gelöschten Knoten zeigt, also ins Nirvana.

    Dazu kommt das Problem mit der Größe: std::list wird vermutlich ihre Größe in einer Variablen speichern. Beide Methoden werden die gleichzeitig inkrementieren und dekrementieren, ein klassischer Lehrbuchfall für eine Race Condition.



  • Danke euch für die schnellen Antworten,

    wie ich sehe hab ich etwas viel übersehen 😉 Aber dafür gibts ja solche Foren...

    Komm wohl über das Kopieren der List nicht drumherum.

    Vielen Dank und viel spass noch beim coden



  • Passend dazu folgener Artikel: Writing Lock-Free Code: A Corrected Queue



  • Wenn ich noch kontrolliere das die max Grösse der Liste nicht überschritten wird, müsste dies doch eigentlich schon synchron sein...

    Nein.

    Oder hab ich da was übersehen?

    Übersehen ist nicht das richtige Wort. Die fehlen nur die Grundlagen um überhaupt beurteilen zu können ob etwas threadsafe ist.

    Davon abgesehen gilt: ob eine Library-Funktion threadsafe ist, und was mit threadsafe gemeint ist, findest du in der Doku. Wenn diesbezüglich nix drinnen steht, musst du davon ausgehen, dass sie nicht threadsafe ist. Da im C++ Standard diesbezüglich nix steht... -> nichts ist threadsafe.



  • Guest_111 schrieb:

    Komm wohl über das Kopieren der List nicht drumherum.

    Warum solltest du die Liste kopieren müssen?



  • @hustbaer
    Hab ja im ersten Satz von diesem Artikel geschrieben das sie nicht ThreadSafe ist...

    @l'abra d'or
    Meine Applikation:
    Thread A empfängt Packete auf einem Socket
    Thread B holt diese immer ab

    Wenn nun ununterbrochen auf dem Socket geschrieben wird, wird der Mutex ja praktisch nie freigegeben... So muss ich das Zeugs kopieren um eine Art "neutrale Liste" zu erhalten...
    --> Thread A empfängt speichert in Liste A, Kopiert Liste A in B
    --> Thread B holt Daten von Liste B

    Gruss



  • Guest_111 schrieb:

    Wenn nun ununterbrochen auf dem Socket geschrieben wird, wird der Mutex ja praktisch nie freigegeben... So muss ich das Zeugs kopieren um eine Art "neutrale Liste" zu erhalten...
    --> Thread A empfängt speichert in Liste A, Kopiert Liste A in B
    --> Thread B holt Daten von Liste B

    Komiker...
    Das umkopieren musst du ja auch mit ner Mutex absichern, sonst stehst du vor dem selben Problem, dass während dem Umkopieren die Liesten manipuliert weden.



  • pumuckl schrieb:

    Guest_111 schrieb:

    Oder hab ich da was übersehen?

    Ja. push_back erzeugt nicht nur einen neuen Listenknoten, sondern verändert auch den letzten Listenknoten (falls vorhanden), nämlich dessen next-Pointer.
    pop_front löscht nicht nur den ersten Listenknoten sondern modifiziert auch noch den zweiten (der zum ersten wird, falls vorhanden), nämlich dessen prev-Pointer.

    Wenn bei einer Liste mit zwei Elementen gleichzeitig push_back und pop_front ausführst, wird also gleichzeitig das zweite Element bearbeitet, wenngleich es unterschiedliche Member des Knotens sind. Klingt irgendwie trotzdem potentiell ungesund.
    Wenn du bei einer Liste mit nur einem Element gleichzeitig push_back und pop_front machst gibts Chaos: der neu hinten angefügte Listenknoten könnte einen prev-Pointer erhalten, der auf den im selben Moment gelöschten Knoten zeigt, also ins Nirvana.

    Dazu kommt das Problem mit der Größe: std::list wird vermutlich ihre Größe in einer Variablen speichern. Beide Methoden werden die gleichzeitig inkrementieren und dekrementieren, ein klassischer Lehrbuchfall für eine Race Condition.

    Der Punkt ist einfach: es macht kaum Sinn, sich über sowas Gedanken zu machen, wenn es nicht garantiert wird. Und das wird es nicht. Das ist auch das was ich mit meinem Beitrag zum Ausdruck bringen wollte.

    Nur weil man sich z.B. vorstellen würde man könnte oder sollte eine std::list so-oder-so implementieren, und dass bei einer solchen Implementierung dieses oder jenes "automatisch" thread-safe wäre, kann man sich noch lange nicht darauf verlassen, dass es das auch wirklich ist.



  • hustbaer schrieb:

    Nur weil man sich z.B. vorstellen würde man könnte oder sollte eine std::list so-oder-so implementieren, und dass bei einer solchen Implementierung dieses oder jenes "automatisch" thread-safe wäre, kann man sich noch lange nicht darauf verlassen, dass es das auch wirklich ist.

    Meine Argumentation war auch exakt gegenteilig. Weil diverse Implementierungen erlaubt und möglich sind, die nicht threadsafe sind, ist std::list auch nicht garantiert threadsafe.



  • pumuckl schrieb:

    hustbaer schrieb:

    Nur weil man sich z.B. vorstellen würde man könnte oder sollte eine std::list so-oder-so implementieren, und dass bei einer solchen Implementierung dieses oder jenes "automatisch" thread-safe wäre, kann man sich noch lange nicht darauf verlassen, dass es das auch wirklich ist.

    Meine Argumentation war auch exakt gegenteilig. Weil diverse Implementierungen erlaubt und möglich sind, die nicht threadsafe sind, ist std::list auch nicht garantiert threadsafe.

    Ist mir schon klar.
    Ich finde nur die Art wie du es dargelegt hast ... potentiell irreführend.

    Im Leser könnte die Idee aufkommen, man müsse sich nur durchdenken wie etwas implementiert sein könnte, und könne daraus schliessen ob es nicht vielleicht auch ohne externe Synchronisierung Thread-Safe sein könnte.

    Und IMO ist das der falsche Ansatz. Es ist für jede Funktion jeder Klasse möglich, eine standardkonforme Implementierung zu schreiben, die nicht Thread-Safe ist. Ganz egal wie einfach die Funktion vielleicht ist.

    Daher ist IMO der einzig sinnvolle Ansatz: "gucken was draufsteht", und sich danach richten.


Anmelden zum Antworten