Ghost_Rider_R schrieb:
Ich hätte jetzt nochmal zwei Fragen zu dem Thema:
1. Ich habe mal einige Versuche gemacht und festgestellt, dass ich auf globale Attribute lesend zugreifen kann, ohne nennenswerte Performanceeinbrüche zu verzeichnen. Sobald ich diese aber modifiziere bricht die Performance massivst ein. Das Verhalten ist dann normal, so wie ich das jetzt verstanden habe oder?
2. Ich habe das Beispiel nochmal angepasst und geschaut, was passiert, wenn ich eine Methode zum ,,Vergleichen" verwende und was passiert, wenn ich den Methodeninhalt direkt innerhalb eines Threads ausführe, also ohne einen Methodenaufruf.
1. Das ist alles nicht so einfach. Vor allem, weil du dir ein Beispiel herausgesucht hast, wo es scheinbar oder aber anscheinden zufällig funktioniert.
Sobald auf geteilte (also "shared") Ressourcen (egal von wem, da spielt der Main-Thread auch mit!) sowohl lesend, als auch schreibend zugegriffen wird müssen diese unbedingt abgesichert werden.
Egal, ob beim Lesen oder Schreiben und auch egal, wie oft das eine oder das andere tatsächlich passiert.
(Es gibt Ausnahmen, die setzen aber ein umfassendes Verständnis des verwendeten Datentyps, der damit verbundenen Operationen und der Prozessorarchitektur, auf der alles am Ende ausgeführt wird, voraus.
Ist eines davon nicht gegeben, ist ohne entsprechende Absicherung die Wahrscheinlichkeit von Fehlern sehr groß!)
Stichwörter dafür wären z.B.
Critical Section oder
Mutex.
Beim Schreiben von geteileten Ressourcen hast du ja schon Erfahrungen gemacht, was da passieren kann.
Was beim Lesen passieren kann, wäre z.B. folgendes:
Nehmen wir mal an, es braucht zwei Instruktionen, um deiner Variable eines bestimmten Datentypen einen neuen Wert zuzuweisen.
Die erste Instruktion überschreibt dabei die erste hälfte dieser Variable im Speicher und die zweite Instruktion die zweite Hälfte.
Gehen wir davon aus, dass diese zwei Schreibvorgänge von keiner Seite her irgendwie abgesichert werden, dann steht es im Raum des Möglichen, dass das Betriebsystem direkt nach dem Schreiben der ersten Hälfte einen Kontext-Switch vom schreibenden Thread hin zu einen lesenden Thread vollziehen könnte.
Das würde dazu führen, dass der lesende Thread für deine Variable nun einen Wert bezieht, der zum einen Teil aus dem neuen und zum anderen Teil als aus dem alten Wert besteht.
Das ist jetzt bei Ordinalen Datentypen, wie Integern, Booleans, Chars o.ä., eher unwahrscheinlich, wird aber bei Behandlung von großen Datentypen (je nach Implementation z.B. sehr große (128bit+) Integer, Floats, etc.), Records/Structs oder gleich Objekten immer realistischer.
Eine Möglichkeit Absicherungen zu umgehen gibt es nur in der Form, geteilte Ressourcen zu vermeiden und stattdessen alle notwendigen Daten für jeden einzelnen Thread zu kopieren.
Dann kann jeder Thread machen, was er machen soll und am Ende der Thread-Lebenszeit (bzw. nach Task-Abschluss) können die Ergebnise gefahrenlos von einem einzelnen Thread (z.B. dem Main-Thread) gelesen und mit anderen Ergebnissen vereint werden.
Sowas geht aber natürlich nicht immer.
Dieser Ansatz ist aber vorzuziehen, da das noch einen anderen Vorteil hat:
Multithreading heißt nicht gleich auch automatisch "Multi-Core-Threading".
Ob ein Thread auf einem eigenen bzw. unabhängigen logischen oder physischen CPU-Kern ausgeführt wird, entscheidet das Betriebsystem und diese Entscheidung trifft das OS unter anderm anhand von Zugriffen auf Ressourcen.
Erkennt das OS dabei, dass der Thread in kurzen Abständen immer auch auf dieselben Ressourcen zugreift, wie z.B. dein Main-Thread (oder anders herum), kann das OS zur (wahrscheinlich richtigen) Einschätzung kommen, dass es unterm Strich Rechenintensiver ist, die Daten immer wieder in den kurzen Abständen zwischen den Registern/Caches zweier Kerne hin- und herzusynchonisieren, als beides auf demselben Kern laufen zu lassen.
2. Da muss noch irgend etwas anderes hineinspielen.
C# nutzt i.d.R. einen JIT-Compiler, der aus Code, hinterlegt in
CIL (Common Intermediate Language), erst dann Maschinencode produziert, wenn er tatsächlich ausgeführt wird.
Aber ich kann mir kaum vorstellen, dass diese sehr kleine Methode einmalig so viel Zeit frisst, auch nicht in Verbindung mit dem Kopieren des Strings, der als Parameter übergeben wird, dem Aufbau des Aufrufstacks den Sprungmarkern für den Methodenaufruf und dem kopieren des Rückgabewertes.