Threadsicherheit-Problem


  • Administrator

    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 und Task sind beides Klassen von mir. Ich wusste bis jetzt nicht mal, dass sowas mit .Net 4.0 kommt 😃

    ConcurrentQueue 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 ist Task ?

    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





  • 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;
    			}
    		}
    

  • Administrator

    @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 Klasse Interlocked geführt. Da gibt es eine Methode Exchange<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


  • Administrator

    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


  • Administrator

    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.
    Mit volatile 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.


Anmelden zum Antworten