Ich habe eine Liste, aus der verschiedene Threads Elemente abrufen sollen. Um zu vermeiden, dass der Mutex die Liste schützt, wenn er leer ist, überprüfe ich dies empty()
vor dem Sperren.
Es ist in Ordnung, wenn der Anruf zu list::empty()
100% nicht richtig ist. Ich möchte nur vermeiden, dass gleichzeitig list::push()
und bei list::pop()
Anrufen abgestürzt oder gestört wird .
Kann ich davon ausgehen, dass VC ++ und Gnu GCC nur manchmal empty()
falsch liegen und nichts Schlimmeres?
if(list.empty() == false){ // unprotected by mutex, okay if incorrect sometimes
mutex.lock();
if(list.empty() == false){ // check again while locked to be certain
element = list.back();
list.pop_back();
}
mutex.unlock();
}
c++
multithreading
optimization
race-condition
stdlist
Anne Quinn
quelle
quelle
std::list::size
eine konstante zeitliche Komplexität garantiert hat, was im Grunde bedeutet, dass die Größe (Anzahl der Knoten) in einer separaten Variablen gespeichert werden muss. Nennen wir essize_
.std::list::empty
dann wird wahrscheinlich etwas zurückgegeben alssize_ == 0
, und das gleichzeitige Lesen und Schreiben vonsize_
würde Datenrennen verursachen, daher UB.Antworten:
Nein, das ist nicht in Ordnung. Wenn Sie überprüfen, ob die Liste außerhalb eines Synchronisationsmechanismus (Sperren des Mutex) leer ist, haben Sie ein Datenrennen. Ein Datenrennen bedeutet, dass Sie ein undefiniertes Verhalten haben. Undefiniertes Verhalten bedeutet, dass wir nicht mehr über das Programm nachdenken können und jede Ausgabe, die Sie erhalten, "korrekt" ist.
Wenn Sie Wert auf Ihre geistige Gesundheit legen, nehmen Sie den Leistungstreffer und sperren den Mutex, bevor Sie ihn überprüfen. Die Liste ist jedoch möglicherweise nicht einmal der richtige Container für Sie. Wenn Sie uns genau mitteilen können, was Sie damit machen, können wir möglicherweise einen besseren Container vorschlagen.
quelle
list::empty()
ist eine Leseaktion, die nichts damit zu tun hatrace-condition
read-write
oderwrite-write
. Wenn Sie mich nicht glauben, gibt dem Standardteil auf Daten Rennen eine LeseEs gibt ein Lesen und ein Schreiben (höchstwahrscheinlich für das
size
Mitglied vonstd::list
, wenn wir davon ausgehen, dass es so benannt ist), die im Reader nicht miteinander synchronisiert sind . Stellen Sie sich vor, ein Thread ruftempty()
(in Ihrem äußerenif()
) auf, während der andere Thread in den innerenif()
eintritt und ausgeführt wirdpop_back()
. Sie lesen dann eine Variable, die möglicherweise geändert wird. Dies ist undefiniertes Verhalten.quelle
Als Beispiel dafür, wie etwas schief gehen könnte:
Ein ausreichend intelligenter Compiler könnte erkennen, dass
mutex.lock()
derlist.empty()
Rückgabewert möglicherweise nicht geändert werden kann, und somit die innereif
Prüfung vollständig überspringen , was schließlich zupop_back
einer Liste führt, deren letztes Element nach dem ersten entfernt wurdeif
.Warum kann es das tun? Es findet keine Synchronisation statt.
list.empty()
Wenn es also gleichzeitig geändert würde, würde dies ein Datenrennen darstellen. Der Standard besagt, dass Programme keine Datenrennen haben dürfen, daher nimmt der Compiler dies als selbstverständlich an (andernfalls könnte er fast keine Optimierungen durchführen). Daher kann es eine Single-Threaded-Perspektive auf das nicht synchronisierte annehmenlist.empty()
und daraus schließen, dass es konstant bleiben muss.Dies ist nur eine von mehreren Optimierungen (oder Hardware-Verhaltensweisen), die Ihren Code beschädigen könnten.
quelle
a.load()+a.load()
...a.load()*2
offensichtlich ist? Nicht einmala.load(rel)+b.load(rel)-a.load(rel)
ist sowieso optimiert. Nichts ist. Warum erwarten Sie, dass Sperren (die im Wesentlichen meistens die Konsistenz von seq aufweisen) optimiert werden?a.load() + a.load()
zu2 * a.load()
. Sie können gerne eine Frage dazu stellen, wenn Sie mehr wissen möchten.