Können flüchtige, aber nicht umschlossene Lesevorgänge auf unbestimmte Zeit veraltete Werte liefern? (auf echter Hardware)
Bei der Beantwortungdiese Frage Eine weitere Frage zur Situation des OP kam auf, bei der ich mir nicht sicher war: Es handelt sich hauptsächlich um eine Frage zur Prozessorarchitektur, aber auch um eine Zusatzfrage zum C ++ 11-Speichermodell.
Grundsätzlich war der OP-Code aufgrund des folgenden Codes (der Einfachheit halber leicht modifiziert) auf höheren Optimierungsebenen unendlich geloopt:
while (true) {
uint8_t ov = bits_; // bits_ is some "uint8_t" non-local variable
if (ov & MASK) {
continue;
}
if (ov == __sync_val_compare_and_swap(&bits_, ov, ov | MASK)) {
break;
}
}
woher__sync_val_compare_and_swap()
ist GCC Atomic CAS eingebaut. GCC optimierte dies (vernünftigerweise) in diesem Fall zu einer Endlosschleifebits_ & mask
wurde erkannt zu seintrue
Bevor Sie in die Schleife eintreten, überspringen Sie die CAS-Operation vollständig und ich schlug die folgende Änderung vor (die funktioniert):
while (true) {
uint8_t ov = bits_; // bits_ is some "uint8_t" non-local variable
if (ov & MASK) {
__sync_synchronize();
continue;
}
if (ov == __sync_val_compare_and_swap(&bits_, ov, ov | MASK)) {
break;
}
}
Nachdem ich geantwortet hatte, bemerkte OP, dass sich dies ändertebits_
zuvolatile uint8_t
scheint auch zu funktionieren. Ich schlug vor, diesen Weg nicht zu gehenvolatile
sollte normalerweise nicht für die Synchronisation verwendet werden, und es scheint ohnehin nicht viel nachteilig zu sein, hier einen Zaun zu verwenden.
Ich habe mir jedoch mehr Gedanken darüber gemacht, und in diesem Fall ist die Semantik so, dass es eigentlich egal ist, ob dieov & MASK
Die Prüfung basiert auf einem veralteten Wert, solange er nicht auf einem unbegrenzt veralteten Wert basiert (d. h. solange die Schleife irgendwann unterbrochen wird), seit dem tatsächlichen Aktualisierungsversuchbits_
synchronisiert ist. So ist es auchvolatile
hier genug, um sicherzustellen, dass diese Schleife schließlich endet, wennbits_
wird von einem anderen Thread so aktualisiert, dassbits_ & MASK == false
für irgendeinen existierenden Prozessor? Mit anderen Worten, ist es ohne expliziten Speicherbereich praktisch möglich, dass nicht vom Compiler optimierte Lesevorgänge vom Prozessor effektiv und unbegrenzt herausoptimiert werden? (BEARBEITEN: Um es klar auszudrücken, frage ich hier, was moderne Hardware unter der Annahme, dass Lesevorgänge vom Compiler in einer Schleife ausgegeben werden, tatsächlich tun könnte. Es handelt sich also technisch gesehen nicht um eine Sprachfrage, obwohl es praktisch ist, sie in C ++ - Semantik auszudrücken.)
Das ist der Hardware-Winkel dazu, aber um es leicht zu aktualisieren und es auch zu einer beantwortbaren Frage zum C ++ 11-Speichermodell zu machen, sollten Sie die folgende Variation des obigen Codes berücksichtigen:
// bits_ is "std::atomic<unsigned char>"
unsigned char ov = bits_.load(std::memory_order_relaxed);
while (true) {
if (ov & MASK) {
ov = bits_.load(std::memory_order_relaxed);
continue;
}
// compare_exchange_weak also updates ov if the exchange fails
if (bits_.compare_exchange_weak(ov, ov | MASK, std::memory_order_acq_rel)) {
break;
}
}
cppreference behauptet, dassstd::memory_order_relaxed
impliziert "keine Einschränkungen bei der Neuordnung von Speicherzugriffen um die atomare Variable", was unabhängig davon ist, was die tatsächliche Hardware tun oder nicht tun wirdbits_.load(std::memory_order_relaxed)
könnte technischnoch nie Lesen Sie einen aktualisierten Wert nachbits_
wird in einem anderen Thread in einer konformen Implementierung aktualisiert?
BEARBEITEN: Ich fand dies im Standard (29.4 S. 13):
Implementierungen sollten Atomspeicher innerhalb eines angemessenen Zeitraums für Atomlasten sichtbar machen.
Offensichtlich kommt es (meistens?) Nicht in Frage, "unendlich lange" auf einen aktualisierten Wert zu warten, aber es gibt keine Garantie für ein bestimmtes Zeitintervall der Frische, außer dass dies "angemessen" sein sollte. nach wie vor steht die frage nach dem tatsächlichen hardwareverhalten.