Bedeutet const in C ++ 11 threadsicher?

115

Ich höre, dass constdies in C ++ 11 threadsicher bedeutet . Ist das wahr?

Heißt das constjetzt das Äquivalent von Java ‚s synchronized?

Gehen ihnen die Schlüsselwörter aus ?

K-Ballo
quelle
1
Die C ++ - FAQ wird in der Regel von der C ++ - Community verwaltet. Sie können uns in unserem Chat um Meinungen bitten.
Welpe
@DeadMG: Ich war mir der C ++ - FAQ und ihrer Etikette nicht bewusst, es wurde in einem Kommentar vorgeschlagen.
K-Ballo
2
Wo haben Sie gehört, dass const threadsicher bedeutet?
Mark B
2
@ Mark B: Herb Sutter und Bjarne Stroustrup sagten dies bei der Standard C ++ Foundation , siehe den Link am Ende der Antwort.
K-Ballo
HINWEIS FÜR DIE, DIE HIER KOMMEN: Die eigentliche Frage ist NICHT, ob const dies threadsicher bedeutet . Das wäre Unsinn, da es sonst bedeuten würde, dass Sie in der Lage sein sollten, jede thread-sichere Methode als zu markieren const. Die Frage, die wir wirklich stellen, ist const IMPLIES threadsicher, und darum geht es in dieser Diskussion.
user541686

Antworten:

131

Ich höre, dass constdies in C ++ 11 threadsicher bedeutet . Ist das wahr?

Es ist etwas wahr ...

Dies ist, was die Standardsprache zur Thread-Sicherheit zu sagen hat:

[1.10 / 4] Zwei Ausdrucksbewertungen stehen in Konflikt, wenn einer von ihnen einen Speicherort (1.7) ändert und der andere auf denselben Speicherort zugreift oder diesen ändert.

[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 nicht atomar ist und keine vor der anderen stattfindet. Ein solches Datenrennen führt zu undefiniertem Verhalten.

das ist nichts anderes als die hinreichende Bedingung für ein Daten Rennen auftreten:

  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 Standardbibliothek baut darauf auf und geht noch ein bisschen weiter:

[17.6.5.9/1] In diesem Abschnitt werden die Anforderungen festgelegt, die Implementierungen erfüllen müssen, um Datenrennen zu verhindern (1.10). Jede Standardbibliotheksfunktion muss jede Anforderung 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 Objekte (1.10), auf die andere Threads als der aktuelle Thread zugreifen , nicht direkt oder indirekt ändern, es sei denn, auf die Objekte wird direkt oder indirekt über die nicht konstanten Argumenteder Funktion zugegriffen, einschließlichthis.

was in einfachen Worten besagt, dass erwartet wird, dass Operationen an constObjekten threadsicher sind . Dies bedeutet, dass die Standardbibliothek kein Datenrennen einführt, solange Operationen an constObjekten Ihres eigenen Typs ausgeführt werden

  1. Besteht ausschließlich aus Lesungen - das heißt, es gibt keine Schreibvorgänge -; oder
  2. Synchronisiert intern Schreibvorgänge.

Wenn diese Erwartung für einen Ihrer Typen nicht gilt, kann die direkte oder indirekte Verwendung zusammen mit einer Komponente der Standardbibliothek zu einem Datenrennen führen . Zusammenfassend constbedeutet dies aus Sicht der Standardbibliothek threadsicher . Es ist wichtig zu beachten, dass dies lediglich ein Vertrag ist und vom Compiler nicht durchgesetzt wird. Wenn Sie ihn brechen, erhalten Sie undefiniertes Verhalten und sind auf sich allein gestellt. Ob vorhanden ist oder nicht , wird nicht die Codegenerierung --at dest nicht in Bezug auf die Auswirkungen auf Daten Rennen -.const

Heißt das constjetzt das Äquivalent von Java ‚s synchronized?

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 ist const, sondern weil es ausschließlich aus Leseoperationen besteht. Es sind keine Schreibvorgänge beteiligt, und mindestens ein Schreibvorgang ist erforderlich, damit ein Datenrennen stattfinden kann. Das bedeutet, dass Sie areavon so vielen Threads aus aufrufen können, wie Sie möchten, und dass Sie jederzeit korrekte Ergebnisse erhalten.

Beachten Sie, dass dies nicht bedeutet , dass rectist Thread-sicher . In der Tat, um zu sehen , ist es einfach , wie wenn ein Aufruf areazur gleichen Zeit geschehen sollte , dass ein Aufruf set_sizezu einem bestimmten rect, dann areakönnte am Ende das Ergebnis basiert auf einer alten Breite und eine neue Höhe (oder sogar auf verstümmelte Werte) Berechnen .

Aber das ist in Ordnung, es rectist nicht constso, dass nicht einmal erwartet wird, dass es threadsicher ist . Ein deklariertes Objekt const rectwäre dagegen threadsicher, da keine Schreibvorgänge möglich sind (und wenn Sie überlegen, const_castetwas ursprünglich Deklariertes zu consttun, erhalten Sie ein undefiniertes Verhalten, und das war's).

Was bedeutet es dann?

Nehmen wir aus Gründen der Argumentation an, dass Multiplikationsoperationen extrem kostspielig sind und wir sie besser vermeiden, wenn dies möglich ist. Wir könnten den Bereich nur berechnen, wenn er angefordert wird, und ihn dann zwischenspeichern, falls 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 es mental intdurch eine sehr große dynamisch zugewiesene Ganzzahl ersetzen, die von Natur aus nicht threadsicher ist und für die Multiplikationen äußerst kostspielig sind.]

Die Member-Funktion area ist nicht mehr threadsicher , sie schreibt jetzt und ist nicht intern synchronisiert. Ist es ein Problem? Der Aufruf areaals Teil eines passieren kann Kopie-Konstruktor eines anderen Objekts, wie Konstruktor könnte durch eine Operation an einem genannt wurden Standard - Container , und an diesem Punkt die Standardbibliothek diese Operation erwartet als verhalten Lese in Bezug auf Daten Rennen . Aber wir schreiben!

Sobald wir einen direkt oder indirekt rectin einen Standardcontainer legen, schließen wir einen Vertrag mit der Standardbibliothek . Um weiterhin Schreibvorgänge in einer constFunktion ausführen zu können, 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 areaFunktion threadsicher gemacht haben , die aber rectimmer noch nicht threadsicher ist . Ein Aufruf zur areagleichen Zeit, zu der ein Aufruf an set_sizeimmer noch den falschen Wert berechnet, da die Zuweisungen zu widthund heightnicht durch den Mutex geschützt sind.

Wenn wir wirklich einen Thread-Safe wollten rect, würden wir ein Synchronisationsprimitiv verwenden, um den Nicht-Thread-Safe zu schützen rect.

Gehen ihnen die Schlüsselwörter aus ?

Ja, sind Sie. Seit dem ersten Tag gehen ihnen die Schlüsselwörter aus .


Quelle : Sie wissen es nicht constundmutable - Herb Sutter

K-Ballo
quelle
6
@ Ben Voigt: Nach meinem Verständnis ist die C ++ 11- Spezifikation für std::stringso formuliert, dass COW bereits verboten ist . Ich erinnere mich jedoch nicht an die Einzelheiten ...
K-Ballo
3
@ BenVoigt: Nein. Es würde lediglich verhindern, dass solche Dinge nicht synchronisiert werden, dh nicht threadsicher. C ++ 11 verbietet COW bereits explizit - diese spezielle Passage hat jedoch nichts damit zu tun und würde COW nicht verbieten.
Welpe
2
Es scheint mir, dass es eine logische Lücke gibt. [17.6.5.9/3] verbietet "zu viel", indem er sagt "es darf nicht direkt oder indirekt modifizieren"; es sollte heißen "soll nicht direkt oder indirekt ein Datenrennen einführen", es sei denn, ein atomares Schreiben ist irgendwo definiert, um nicht "modifizieren" zu sein. Aber ich kann das nirgendwo finden.
Andy Prowl
1
Wahrscheinlich habe ich hier meinen ganzen Punkt etwas klarer formuliert : isocpp.org/blog/2012/12/… Vielen Dank, dass Sie versucht haben, trotzdem zu helfen.
Andy Prowl
1
manchmal frage ich mich, wer derjenige war (oder diejenigen, die direkt beteiligt waren), der tatsächlich dafür verantwortlich war, einige Standardabsätze wie diese aufzuschreiben.
Pepper_chico