Hallo Th69,
Zitat: |
Und mit Sichtbarkeit auch nicht |
Entsprechend dem Speichermodell (memory model) der CPU / Threads geht es bei
volatile genau um die Sichtbarkeit (der Werte).
Z.B. dass der Wert nicht in einem Register gehalten werden darf, und somit "unsichtbar" für andere Threads ist, sondern "sichtbar" im Speicher sein muss. Mit "Speicher" sind hier auch allfällige Cache-Hierarchien gemeint und da geht es erst recht um die Sichtbarkeit (der Änderungen).
Zusätzlich wird beim Lesen ein "acquire fence" und ein "release fence" beim Schreiben gesetzt, so dass andere Lese-/Schreibvorgänge in Bezug auf den "fence" nicht umgeordnet werden dürfen. Auch dabei geht es um die Sichtbarkeit.
(Anm.: die "fences" sind auch Begriffe aus dem Speichermodell).
Vllt. hätte ich in der vorigen Antwort bei Sichtbarkeit den Bezug zum Speichermodell explizit herstellen sollen, denn mit Sichtbarkeit kann auch mehr gemeint sein (wie
public,
private, etc.) und somit hast du (in dieser Hinsicht) recht ;-)
Zitat: |
der Compiler bzw. das Runtime-System |
Auch die CPU ist dabei betroffen.
Der/die Compiler dürfen beim Optimieren den Code nur so verändern, dass die "fences" von volatile erhalten bleiben.
Die Runtime / Execution Engine / VM müssen auch die "fences" erhalten lassen.
Die CPU muss dafür sorgen, dass in Bezug auf die "fences" die Sichtbarkeit des Speicherbereichs gegeben ist und darf keine Pipeline-Optimierungen vornehmen welche eine "fence" verletzen würden.
Intel x84/x64 ist hier von Haus aus eher streng ("strong memory model"), aber z.B. Arm-Prozessoren sind hier sehr locker ("weak memory model"). Daher ist für plattformübergreifenden Code und Threading die Kenntnis der Speichermodelle -- zu einem bestimmten Grade -- nicht ganz unwesentlich.
Einschub:
Kestrel (ein Server von ASP.NET Core) lief anfänglich nur auf x86*. Da in der Cloud immer mehr Arm64-CPUs Einzug halten, gab es folglich den Wunsch dass Kestrel auch Arm64 unterstützt. Wie sich jeder vorstellen kann sind Webserver sehr nebenläufig und parallelisiert. Ein großer Punkt für die Unterstützung von Arm64 war das "weak memory Model" und die korrekte Handhabung der Speicherzugriffen.
Neben ausführlichen Reviews für die Pull Requests werden zur Überprüfung auch jede Menge "Fuzz Tests" durchgeführt, denn gerade bei "Threading" ist nur durch "Hinschauen" nciht so einfach potentielle Fallstricke erkennen zu können.
* wird nur x86 angegeben, so sind Intel kompatible 32-/64-bit Systeme gemeint.
Andernfalls wird explizit von x86 und x64 od. Arm64 / Arm32, etc. gesprochen.
Zitat: |
volatile kann eben nur auf "atomare" Datentypen angewendet |
Danke dass du das ergänzt, ist aber auch nicht ganz korrekt / präzise (obwohl ich deine Intention dahinter sehr wohl verstehe).
volatile für Datentypen die größer als die Wortbreite der CPU sind machen keinen Sinn, da so das Prinzip von volatile nicht umgesetzt werden kann.
Dass der C#-Compiler hier
long,
double ausnimmt, liegt darin begründet dass .NET Anwendung plattformunabhängig laufen sollen, d.h. sowohl auf x86 und x64 und auf 32-bit Systemen (x86) sind diese beiden Datentypen nicht atomar.
Daher auch das "fast" oben, denn auf x64 sind double und long atomare Datentypen, dennoch kann volatile nicht darauf angewandt werden.
Gleiches gilt für atomare Werttypen auf die volatile nicht angewandt werden kann. Z.B.
C#-Code: |
public struct Foo
{
public int A;
}
|
ist perfekt atomar, dennoch geht volatile nicht.
Als ich meine Antwort verfasste sah ich "atomar" nur in Bezug auf das von mir Zitierte.
Da sehe ich diesen Punkt immer noch als passend an, aber allgemein gesehen ist dieser Punkt zu unpräzise.
Ich bin immer wieder froh, dass es ein Forum gibt welches solche unpräzisen Punkte aufgreift :-)
Hallo Palladin007,
Zitat: |
Das sind so Details, die ich mir von so einer Lektüre erhofft hätte |
Wenn du einen Punkt hast der dich interessiert, so kannst du ja eine Frage im Forum stellen. Irgendwer wird schon eine Antwort schreiben... ;-)
mfG Gü