Ich höre, dass const
dies 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:
- Es werden zwei oder mehr Aktionen gleichzeitig für eine bestimmte Sache ausgeführt. und
- 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 const
Objekten threadsicher sind . Dies bedeutet, dass die Standardbibliothek kein Datenrennen einführt, solange Operationen an const
Objekten Ihres eigenen Typs ausgeführt werden
- Besteht ausschließlich aus Lesungen - das heißt, es gibt keine Schreibvorgänge -; oder
- 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 const
bedeutet 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 const
jetzt 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 area
von 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 rect
ist Thread-sicher . In der Tat, um zu sehen , ist es einfach , wie wenn ein Aufruf area
zur gleichen Zeit geschehen sollte , dass ein Aufruf set_size
zu einem bestimmten rect
, dann area
kö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 rect
ist nicht const
so, dass nicht einmal erwartet wird, dass es threadsicher ist . Ein deklariertes Objekt const rect
wäre dagegen threadsicher, da keine Schreibvorgänge möglich sind (und wenn Sie überlegen, const_cast
etwas ursprünglich Deklariertes zu const
tun, 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 int
durch 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 area
als 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 rect
in einen Standardcontainer legen, schließen wir einen Vertrag mit der Standardbibliothek . Um weiterhin Schreibvorgänge in einer const
Funktion 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 area
Funktion threadsicher gemacht haben , die aber rect
immer noch nicht threadsicher ist . Ein Aufruf zur area
gleichen Zeit, zu der ein Aufruf an set_size
immer noch den falschen Wert berechnet, da die Zuweisungen zu width
und height
nicht 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 const
undmutable
- Herb Sutter