it-swarm.com.de

Bedeutet const thread-safe in C ++ 11?

Ich höre, dass constthread-safe in C++ 11 bedeutet. Ist das wahr?

Bedeutet das, dass const jetzt das Äquivalent von Java 's synchronized ist?

Ist ihnen keywords ausgegangen?

115
K-ballo

Ich höre, dass const thread-safe in C++ 11 bedeutet. Ist das wahr?

Es ist etwas wahr ...

Dies ist, was die Standardsprache über Threadsicherheit zu sagen hat:

[1.10/4] Zwei Ausdrucksauswertungen Konflikt wenn einer von ihnen einen Speicherort (1.7) ändert und der andere auf denselben Speicherplatz zugreift oder ihn ändert Ort.

[1.10/21] Die Ausführung eines Programms enthält ein Datenrennen wenn es zwei widersprüchliche Aktionen in verschiedenen Threads enthält, von denen mindestens eine widersprüchlich ist nicht atomar und auch nicht vor dem anderen. Ein solches Datenrennen führt zu undefiniertem Verhalten.

das ist nichts anderes als die ausreichende Bedingung für das Auftreten eines Datenrennens :

  1. Es werden zwei oder mehr Aktionen gleichzeitig für eine bestimmte Sache ausgeführt. und
  2. Mindestens einer von ihnen ist ein Schreiben.

Die Standard Library baut darauf auf und geht noch ein bisschen weiter:

[17.6.5.9/1] Dieser Abschnitt legt Anforderungen fest, die Implementierungen erfüllen müssen, um Datenrennen zu verhindern (1.10). Jede Standardbibliotheksfunktion muss alle Anforderungen erfüllen, sofern nicht anders angegeben. Implementierungen können Datenrennen in anderen als den unten angegebenen Fällen verhindern.

[17.6.5.9/3] Eine C++ - Standardbibliotheksfunktion darf keine Objekte (1.10) direkt oder indirekt ändern, auf die andere Threads als der aktuelle Thread zugreifen können, es sei denn, die Objekte sind Der Zugriff erfolgt direkt oder indirekt über die Nicht - const -Argumente der Funktion, einschließlich this.

was in einfachen Worten besagt, dass es erwartet, dass Operationen an const Objekten thread-sicher sind. Dies bedeutet, dass die Standard Library kein Datenrennen einführt, solange Operationen an const Objekten Ihres eigenen Typs durchgeführt werden

  1. Bestehen Sie ganz aus Lesungen - das heißt, es gibt keine Schreibvorgänge -; oder
  2. Synchronisiert Schreibvorgänge intern.

Wenn diese Erwartung für einen Ihrer Typen nicht zutrifft, kann die direkte oder indirekte Verwendung mit einer beliebigen Komponente der Standardbibliothek zu einem Datenrennen führen. Zusammenfassend bedeutet const threadsicher aus der Standardbibliothek Sicht. Es ist wichtig zu beachten, dass dies nur ein Vertrag ist und vom Compiler nicht erzwungen wird. Wenn Sie ihn brechen, erhalten Sie undefiniertes Verhalten und Sie sind auf sich alleine gestellt. Ob const vorhanden ist oder nicht, hat keinen Einfluss auf die Codegenerierung - zumindest nicht in Bezug auf Datenrassen -.

Bedeutet das, dass const jetzt das Äquivalent von Java 's synchronized ist?

Nein . Überhaupt nicht...

Betrachten Sie die folgende stark vereinfachte Klasse, die ein Rechteck darstellt:

class rect {
    int width = 0, height = 0;

public:
    /*...*/
    void set_size( int new_width, int new_height ) {
        width = new_width;
        height = new_height;
    }
    int area() const {
        return width * height;
    }
};

Die Member-Funktion area ist threadsicher ; nicht weil es const ist, sondern weil es nur aus Leseoperationen besteht. Es sind keine Schreibvorgänge erforderlich, und mindestens ein Schreibvorgang ist erforderlich, damit ein Datenrennen stattfindet. Das bedeutet, dass Sie area von so vielen Threads aufrufen können, wie Sie möchten, und die ganze Zeit über korrekte Ergebnisse erhalten.

Beachten Sie, dass dies nicht bedeutet, dass rect thread-safe ist. In der Tat ist es einfach zu sehen, wie ein Aufruf von area zur selben Zeit erfolgen würde wie ein Aufruf von set_size Auf einem gegebenen rect, dann area könnte dazu führen, dass das Ergebnis auf der Basis einer alten Breite und einer neuen Höhe (oder sogar auf der Basis verstümmelter Werte) berechnet wird.

Aber das ist in Ordnung, rect ist nicht const, daher wird nicht einmal erwartet, dass es thread-safe ist. Ein Objekt, das als const rect Deklariert wurde, wäre andererseits threadsicher , da keine Schreibvorgänge möglich sind (und wenn Sie const_cast In Betracht ziehen - etwas, das ursprünglich als const dann bekommst du undefined-behaviour und das wars).

Was bedeutet es dann?

Nehmen wir aus Gründen der Argumentation an, dass Multiplikationsoperationen extrem kostspielig sind und wir sie nach Möglichkeit besser vermeiden. Wir können den Bereich nur dann berechnen, wenn er angefordert wird, und ihn dann zwischenspeichern, wenn er in Zukunft erneut angefordert wird:

class rect {
    int width = 0, height = 0;

    mutable int cached_area = 0;
    mutable bool cached_area_valid = true;

public:
    /*...*/
    void set_size( int new_width, int new_height ) {
        cached_area_valid = ( width == new_width && height == new_height );
        width = new_width;
        height = new_height;
    }
    int area() const {
        if( !cached_area_valid ) {
            cached_area = width;
            cached_area *= height;
            cached_area_valid = true;
        }
        return cached_area;
    }
};

[Wenn dieses Beispiel zu künstlich erscheint, können Sie int mental durch eine sehr große dynamisch zugewiesene Ganzzahl ersetzen, die inhärent nicht thread-sicher ist und für die Multiplikationen äußerst kostspielig sind.]

Die Member-Funktion area ist nicht mehr thread-sicher , sie schreibt jetzt und ist nicht intern synchronisiert. Ist es ein Problem? Der Aufruf von area kann als Teil eines Kopierkonstruktors eines anderen Objekts erfolgen, z. B. eines Konstruktors , der durch eine Operation in einem Standardcontainer und bei aufgerufen wurde An diesem Punkt erwartet die Standardbibliothek , dass sich diese Operation in Bezug auf Datenrassen wie ein Lesen verhält. Aber wir schreiben!

Sobald wir ein rect in einen Standardcontainer einfügen - direkt oder indirekt - geben wir einen Vertrag mit der Standardbibliothek ein. Um weiterhin Schreibvorgänge in einer const - Funktion auszuführen, während dieser Vertrag weiterhin eingehalten wird, müssen diese Schreibvorgänge intern synchronisiert werden:

class rect {
    int width = 0, height = 0;

    mutable std::mutex cache_mutex;
    mutable int cached_area = 0;
    mutable bool cached_area_valid = true;

public:
    /*...*/
    void set_size( int new_width, int new_height ) {
        if( new_width != width || new_height != height )
        {
            std::lock_guard< std::mutex > guard( cache_mutex );

            cached_area_valid = false;
        }
        width = new_width;
        height = new_height;
    }
    int area() const {
        std::lock_guard< std::mutex > guard( cache_mutex );

        if( !cached_area_valid ) {
            cached_area = width;
            cached_area *= height;
            cached_area_valid = true;
        }
        return cached_area;
    }
};

Beachten Sie, dass wir die Funktion area threadsicher gemacht haben, aber die Funktion rect immer noch nicht threadsicher . Ein Aufruf von area zur gleichen Zeit wie ein Aufruf von set_size Kann immer noch den falschen Wert berechnen, da die Zuweisungen von width und height sind nicht durch den Mutex geschützt.

Wenn wir wirklich ein thread-sicheres rect wollten, würden wir ein Synchronisationsprimitiv verwenden, um das nicht-thread-sichere rect zu schützen.

Fehlen ihnen keywords ?

Ja, sind Sie. Sie haben seit dem ersten Tag keine Keywords mehr.


Quelle : Sie wissen nicht, const und mutable - Herb Sutter

129
K-ballo