Ist VC ++ immer noch Sequentiell-konsistent?

9

Ich sah (die meisten) Herb Sutter ist der atmotische & lt; & gt; Waffenvideo , und ich wollte die "bedingte Sperre" mit einer Schleife innerhalb der Probe testen. Offenbar, obwohl (wenn ich richtig verstehe) der C ++ 11-Standard besagt, dass das folgende Beispiel richtig funktionieren und sequentiell konsistent sein sollte, ist es nicht.

Bevor Sie weiterlesen, ist meine Frage: Ist das korrekt? Ist der Compiler defekt? Ist mein Code kaputt - habe ich hier eine Race Condition, die ich verpasst habe? Wie umgehe ich das?

Ich habe es auf 3 verschiedenen Versionen von Visual C ++ versucht: VC10 Professional, VC11 Professional und VC12 Express (== Visual Studio 2013 Desktop Express).

Unten ist der Code, den ich für Visual Studio 2013 verwendet habe. Für die anderen Versionen habe ich Boost anstelle von Std verwendet, aber die Idee ist die gleiche.

%Vor%

Um die Idee des Codes zusammenzufassen: Die globale Variable a wird durch den globalen Mutex m geschützt. Angenommen, es gibt keine Befehlszeilenargumente ( argc==1 ), ist der Thread, der other() ausführt, der einzige, der auf die globale Variable a zugreifen soll.

Die korrekte Ausgabe des Programms ist 999999 zu drucken.

Aufgrund der Optimierung der Compilerschleife (mit einem Register für In-Loop-Inkremente und am Ende der Schleife, die den Wert in a zurückkopiert) wird a jedoch von der Assembly geändert, obwohl dies nicht angenommen wird zu.

Das ist in allen 3 VC-Versionen passiert, obwohl ich in diesem Codebeispiel in VC12 einige Aufrufe von sleep() einbauen musste, damit es kaputt ging.

Hier ist ein Teil des Assembler-Codes (die Adresse von a in diesem Lauf ist 0x00f65498 ):

Schleifeninitialisierung - Wert von a wird in edi

kopiert %Vor%

Inkrement innerhalb der Bedingung und nach dem Kopieren der Schleife an den Speicherort von a bedingungslos

%Vor%

Und die Ausgabe des Programms ist 0 .

    
Asaf 19.06.2014, 17:49
quelle

2 Antworten

0

Fast einen Monat später hat Microsoft immer noch nicht auf die Fehler in MSDN Connect .

Um die obigen Kommentare (und einige weitere Tests) zusammenzufassen, passiert es anscheinend auch in VS2013 Professional, aber der Fehler tritt nur beim Erstellen für Win32 auf, nicht für x64. Der generierte Assembly-Code in x64 hat dieses Problem nicht. Es scheint also, dass es ein Fehler im Optimierer ist, und dass es in diesem Code keine Wettlaufbedingung gibt.

Scheinbar passiert dieser Fehler auch in GCC 4.8.1, aber nicht in GCC 4.9. (Danke an Voo , nosid und Chris Dodd für all ihre Tests.

Es wurde vorgeschlagen, a als volatile zu markieren. Dies verhindert zwar den Fehler, aber nur, weil es verhindert, dass der Optimierer die Schleifenregisteroptimierung durchführt.

Ich habe eine andere Lösung gefunden: Fügen Sie eine weitere lokale Variable b hinzu und führen Sie bei Bedarf (und unter Sperre) Folgendes aus:

  1. Kopiere a in b
  2. Erhöht b in der Schleife
  3. Kopieren Sie bei Bedarf zurück zu a

Der Optimierer ersetzt die lokale Variable durch ein Register, so dass der Code immer noch optimiert ist, aber die Kopien von und nach a sind bei Bedarf nur und unter lock.

Hier ist der neue Code main() , mit Pfeilen, die die geänderten Zeilen markieren.

%Vor%

Und so sieht der Assembler-Code aus ( &a == 0x000744b0 , b ersetzt durch edi ):

%Vor%

Dies hält die Optimierung und löst (oder arbeitet) das Problem.

    
Asaf 15.07.2014, 18:47
quelle
0

Das "volatile" Schlüsselwort verhindert diese Art der Optimierung. Genau dafür ist es da: Jede Verwendung von 'a' wird genau wie gezeigt gelesen oder geschrieben und nicht in einer anderen Reihenfolge in andere flüchtige Variablen verschoben.

Die Implementierung des Mutex sollte Compiler-spezifische Anweisungen enthalten, um an diesem Punkt einen "Zaun" zu verursachen, der dem Optimierer sagt, keine Anweisungen über diese Grenze neu anzuordnen. Da die Implementierung nicht vom Compiler-Hersteller stammt, wird das vielleicht weggelassen? Ich habe es nie überprüft.

Da 'a' global ist, würde ich generell denken, dass der Compiler vorsichtiger damit umgehen würde. Aber, VS10 weiß nichts über Threads, so dass es nicht berücksichtigt, dass andere Threads es verwenden werden. Da der Optimierer die gesamte Schleifenausführung erfasst, weiß er, dass Funktionen, die innerhalb der Schleife aufgerufen werden, nicht "a" berühren, und das reicht für ihn.

Ich bin nicht sicher, was der neue Standard über die Thread-Sichtbarkeit von anderen globalen Variablen als flüchtig sagt. Das heißt, gibt es eine Regel, die diese Optimierung verhindern würde (obwohl die Funktion vollständig erfasst werden kann, damit sie weiß, dass andere Funktionen das Globale nicht verwenden, muss sie davon ausgehen, dass andere Threads das können)?

Ich schlage vor, den neueren Compiler mit dem vom Compiler bereitgestellten std :: mutex zu testen und zu überprüfen, was der C ++ - Standard und die aktuellen Entwürfe dazu sagen. Ich denke, dass das oben genannte Ihnen helfen sollte zu wissen, nach was man sucht.

-John

    
JDługosz 24.06.2014 04:49
quelle