Threadsicherheit-Problem
-
Hallo zusammen,
Ich entwickle gerade eine Bibliothek mit .Net 3.5 für WinCE. Dabei gibt es einen Thread, welcher Aufträge erledigt. Ich möchte nun diesem Thread unter gewissen Umständen einen Notfall-Auftrag ausführen lassen. Da der Thread allerdings sehr schnell laufen muss, möchte ich auf locking verzichten. Ich habe mir das folgende überlegt, bin mir aber nicht ganz sicher, ob meine Überlegungen korrekt sind.
Task m_emergencyTask; ConcurrentQueue<Task> m_tasks; // ... while(run) { Task currentTask = m_tasks.Take(); // Hier hat es ein locking, der Aufruf erfolgt allerdings // seltener, daher ist es egal. currentTask.Start(); while(currentTask.IsRunning) { currentTask.Step(); // Während der Ausführung der aktuellen Aufgabe, // soll ständig geprüft werden, ob es eine Notfall- // Aufgabe hat. if(m_emergencyTask != null) { // Falls vorhanden, wird die aktuelle Aufgabe // abgebrochen und gewechselt. currentTask.Cancel(); currentTask = m_emergencyTask; currentTask.Start(); m_emergencyTask = null; } } } // Um einen Notfall-Auftrag zu setzen (aus einem anderen Thread), // wird diese Funktion verwendet: bool SetEmergencyTask(Task task) { if(m_emergencyTask != null) { return false; } m_emergencyTask = task; return true; }
Wie man sieht, kann man nur einen Notfall-Auftrag setzen, wenn noch keiner gesetzt wurde, und der Notfall-Auftrag wird erst zurückgesetzt auf
null
, wenn er vom anderen Thread gelesen wurde. Wenn ich keinen Denkfehler gemacht habe, dann sollte dies nie irgendwie krachen ... oder?Grüssli
-
Also ich kann jetzt auf Anhieb nichts entdecken wo was krachen könnte, sollte so funktionieren.
Btw: du schreibst das du mit .NET 3.5 eine Lib schreibst. Meines erachtens kommt die ConcurrentQueue und der Task erst mit 4.0 oder irre ich mich
-
Firefighter schrieb:
Btw: du schreibst das du mit .NET 3.5 eine Lib schreibst. Meines erachtens kommt die ConcurrentQueue und der Task erst mit 4.0 oder irre ich mich
... ROFL ...
ConcurrentQueue
undTask
sind beides Klassen von mir. Ich wusste bis jetzt nicht mal, dass sowas mit .Net 4.0 kommtConcurrentQueue
wird es mit .Net 4.0 geben? Dazu sage ich nur: GEIL! Nur werde ich es in diesem Projekt leider noch nicht verwenden können/dürfen. Und was istTask
?Grüssli
-
Na klar
Task sind die neuen "Threads" und es gibt die ConcurrentQueue sowie Dictionary und Stack
Denke da findest du sicher was dazu
Aber lustiger Zufall :p
-
Sorry für den Doppelpost:
Hier noch paar Infos:http://msdn.microsoft.com/de-de/library/dd267265(VS.100).aspx (Concurrents)
http://msdn.microsoft.com/de-de/library/system.threading.tasks(VS.100).aspx(Tasks)
-
Ich würde hier zumindest Double-Checked Locking verwenden.
ConcurrencyQueue<Task> m_tasks; volatile Task m_emergencyTask; // <- volatile wichtig object m_emergencyTaskLock = new object(); void Run() { while (run) { Task currentTask = m_tasks.Take(); currentTask.Start(); while (currentTask.IsRunning) { currentTask.Step(); // first check without lock if (m_emergencyTask != null) { // then lock, and check again lock (m_emergencyTaskLock) { if (m_emergencyTask != null) { currentTask.Cancel(); currentTask = m_emergencyTask; m_emergencyTask = null; currentTask.Start(); } } } } } } bool SetEmergencyTask(Task task) { // we can afford to always lock here lock (m_emergencyTaskLock) { if (m_emergencyTask != null) return false; m_emergencyTask = task; return true; } }
-
@Firefighter,
Danke für die Links. Muss mir sowieso mal alle neuen Features von .Net 4.0 anschauen, noch gar nicht dazu gekommen.hustbaer schrieb:
Ich würde hier zumindest Double-Checked Locking verwenden.
Und was ist der Grund? Einfach weil du dich dabei dann wohler fühlst oder hast du eine begründete Angst, dass etwas nicht so funktionieren könnte, wie ich das möchte?
Das mit dem
volatile
ist ein guter Hinweis. Abgesehen davon, dass er für sich schon gut ist, hat er mich auch noch auf die KlasseInterlocked
geführt. Da gibt es eine MethodeExchange<T>
, welche atomar und für hohe performance gedacht ist.
Allerdings denke ich nicht, dass ich sowas brauche. Sowas wäre ja nur ein Problem, falls gleichzeit geschrieben werden würde, was ich nicht tue. Aber es ist definitiv eine Klasse, welche man sich merken muss.Grüssli
-
Dravere schrieb:
hustbaer schrieb:
Ich würde hier zumindest Double-Checked Locking verwenden.
Und was ist der Grund? Einfach weil du dich dabei dann wohler fühlst oder hast du eine begründete Angst, dass etwas nicht so funktionieren könnte, wie ich das möchte?
Ich habe mich jetzt dazu nochmals genauer informiert. Anscheinend ist es möglich, dass zum Beispiel auf einem x64 Computer, die 64 Bit Adresse in zwei Schritten kopiert wird. Wenn nun die Adresse im Bereich 0 bis 232-1 liegt, dann könnte der erste Teil der Adresse auf 0 gesetzt werden, so dass die ganze Adresse Nullen enthält, dann erfolgt ein interrupt, ein anderer Thread liest die Adresse, meint sie ist Null, schreibt eine Adresse grösser als 232-1 rein, es erfolgt erneut ein interrupt, der vorherige Thread läuft wieder, überschreibt nun den oberen Teil der Adresse mit Nullen ... und man hat eine abgeschnittene Adresse ...
Dass heisst: Wenn man sicher sein will und sich nicht noch tiefer und genauer mit der Plattform auseinander setzen möchte (und dadurch völlig Plattform gebunden wird) muss wohl "Double-Checked Locking" verwenden.
Danke!
Grüssli
-
Naja ich glaube hustbaer zielte eher auf folgendes Problem ab. Ich demonstriere das mal mit einem anderen Beispiel:
Bsp: 1.
List<int> liste = new List<int>(); if(!liste.Contains(3)) { lock(liste) { liste.Add(3); } }
Bsp: 2
List<int> liste = new List<int>(); if(!liste.Contains(3)) { lock(liste) { if(!liste.Contains(3)) { liste.Add(3); } } }
Zu Bsp 1:
Stell dir vor Thread 1 kommt an und stellt fest das das Element noch nicht drinne ist, Thread 2 kommt ein paar Ticks später und merkt auch das Element ist nicht drinne. Thread 1 erwirkt aber den Lock eher, Thread 2 ist durch den if Block aber schon durch und wartet auf die entsperrung von dem gesperrten Bereich.
Thread 1 hat seine arbeit getan und das Element 3 eingefügt und entsperrt, nun kommt Thread 2 welcher ja gewartet hat und fügt ein zweites mal das Element 3 hinzu obwohl es schon vorhanden ist.
Deshalb das Bsp 2.Thread 1 kommmt wieder an, erwirkt die Sperre und fragt in der Sperre nochmal ab ob das item schon drinne ist, ist es noch nicht wird eingefügt. Thread 2 wartet vor dem Lock weil das Element noch nicht drinne ist, nachdem der Lock aufgehoben wird, darf Thread 2 an die Reihe, dieser prüft aber NOCHMALS ob das element schon eingefügt wurde und siehe da, es ist schon drinne, heißt also hier war vor mir schon jemand, also kann brauch ich nicht nochmal einfügen.
Ich hoffe das ist das was hustbaer meinte:D Zumindest hab ich das aus der Semantik erkannt,weil ich es auch mal so gelernt habe. Ansonsten vergesst den Beitrag :p
-
Firefighter schrieb:
Ich hoffe das ist das was hustbaer meinte:D Zumindest hab ich das aus der Semantik erkannt,weil ich es auch mal so gelernt habe. Ansonsten vergesst den Beitrag :p
Dein Beispiel funktioniert nicht mal mit Double-Checked Locking. Während dem Einfügen von 3 kann die Liste in einen inkonsistenten Zustand kommen, dann erfolgt ein Wechsel der Threads. Wenn der andere Thread dann prüfen will, ob 3 enthalten ist, kann es durchaus passieren, dass plötzlich eine Runtime-Exception fliegt, weil die Liste in einem inkonsistenten Zustand ist.
Such mal im Inet nach Double-Checked Locking. Du findest schöne Beispiele für zum Beispiel Singletons. Wobei man allerdings in C# und Java bessere Möglichkeiten hat, Singletons effizient zu implementieren, statt über Double-Checked Locking zu gehen. Trotzdem sind die Beispiele gut und zeigen gut auf, wo die Problematic liegt.
Mein Problem sieht zwar ein wenig anders aus, aber da das überschreiben einer Reference nicht atomar geschieht, kann es durchaus auch zu ähnlichen Problemen kommen.
Grüssli
-
Ah ok ich verstehe. Aber nagut was meinst du mit Inkonsitenz genau? Ich meine, kann das nicht immer auftreten? Aber danke für die Suchtips, werd gleich mal schauen.
-
Um die Frage was hier nun wirklich passieren könnte genau beantworten zu können, müsste ich mir erst nochmal das Speichermodell von .NET durchlesen, was das für Garantien abgibt.
Ohne
volatile
ist aber schonmal ganz sicher ein Problem.
Mitvolatile
aber ohne Locks könnte gehen, je nachdem was .NET eben garantiert oder nicht.Die Variante die ich dir gezeigt habe ist auf jeden Fall OK.