2012-12-28 6 views
11

sagen, dass ich diese Funktion haben (ich nehme an Cache in einem Thread-Weg bin Zugriff):Prevent zwei Threads einen Codeblock mit dem gleichen Wert eingeben

object GetCachedValue(string id) 
{ 
    if (!Cache.ContainsKey(id)) 
    { 
     //long running operation to fetch the value for id 
     object value = GetTheValueForId(id); 
     Cache.Add(id, value); 
    }  
    return Cache[id]; 
} 

ich zwei Threads verhindern wollen, laufen die "lang andauernder Betrieb "zur gleichen Zeit für den gleichen Wert. Offensichtlich kann ich das Ganze in eine lock() schreiben, aber dann würde die ganze Funktion unabhängig vom Wert blockieren und ich möchte, dass zwei Threads den lang andauernden Vorgang durchführen können, solange sie nach verschiedenen IDs suchen.

Gibt es einen eingebauten Mechanismus Sperren basierend auf einem Wert zu sperren, so ein Thread blockieren kann, während der andere Thread den lange laufenden Betrieb abgeschlossen, so muß ich es nicht zweimal tun (oder N-mal)? Im Idealfall, solange die lang andauernde Operation in einem Thread ausgeführt wird, sollte kein anderer Thread dies für denselben ID-Wert tun können.

Ich könnte meine eigenen rollen, indem ich die IDs in ein HashSet setze und sie dann entferne, sobald die Operation abgeschlossen ist, aber das scheint ein Hack zu sein.

+0

Sie auf String.intern (id) eine Sperre tun könnte. Aber dann solltest du dir verdammt sicher sein, dass deine "id" Zeichenkette in deiner App sehr (!) Einmalig ist;) – igrimpe

Antwort

7

Ich würde Lazy<T> verwenden hier. Der folgende Code sperrt den Cache, setzt Lazy in den Cache und kehrt sofort zurück. Der Long-running-Betrieb wird einmal threadsicher ausgeführt.

new Thread(() => Console.WriteLine("1-" + GetCachedValue("1").Value)).Start(); 
new Thread(() => Console.WriteLine("2-" + GetCachedValue("1").Value)).Start(); 

Lazy<object> GetCachedValue(string id) 
{ 
    lock (Cache) 
    { 
     if (!Cache.ContainsKey(id)) 
     { 
      Lazy<object> lazy = new Lazy<object>(() => 
       { 
        Console.WriteLine("**Long Running Job**"); 
        Thread.Sleep(3000); 
        return int.Parse(id); 
       }, 
       true); 

      Cache.Add(id, lazy); 
      Console.WriteLine("added to cache"); 
     } 
     return Cache[id]; 
    } 
} 
+0

Race Condition: Lesen aus dem Cache gleichzeitig mit dem Schreiben. – usr

+0

@Usr Ich kann 'return Cache [id];' in den 'lock'-Block bewegen, ohne eine Performance zu beeinträchtigen, aber es gibt keine Wettlaufsituation. 'return Cache [id];' ist garantiert eine 'Lazy ' –

+0

Sicher, wollte nur Leute nicht den Code wörtlich kopieren und seltsame Laufzeitfehler haben. – usr

0

Bewegen Sie Ihre Sperre nach unten, wo Ihr Kommentar ist. Ich denke, Sie müssen eine Liste der derzeit ausgeführten lang laufenden Operationen pflegen, und sperren Sie Zugriffe auf diese Liste, und führen Sie nur die GetValueForId, wenn die id Sie suchen nicht in dieser Liste ist. Ich werde versuchen, etwas aufzupeitschen.

Es gibt immer noch das Problem, was der Thread tun wird, während er auf den anderen Thread wartet, um den Wert zu erhalten.

+1

Das wird einen Deadlock verursachen. Nach dem ersten Abrufen wird versucht, m_runningCacheIds zu sperren, das momentan vom zweiten gesperrt ist, während es auf den ersten wartet. – CodeNaked

+0

@CodeNaked - Richtig, ich wollte nicht, dass der Thread tatsächlich an diesem Punkt im Code wartet. Ich hatte gerade noch nicht herausgefunden, wie man es auf das Cache-Ergebnis warten lassen konnte. –

-2

Es ist nicht die eleganteste Lösung in der Welt, aber ich habe, um dieses Problem mit einer Doppelprüfung und einer Sperre bekommen:

object GetCachedValue(string id) 
{ 
    if (!Cache.ContainsKey(id)) 
    { 
     lock (_staticObj) 
     { 
      if (!Cache.ContainsKey(id)) 
      { 
       //long running operation to fetch the value for id 
       object value = GetTheValueForId(id); 
       Cache.Add(id, value); 
      } 
     } 
    }  
    return Cache[id]; 
} 
+0

Das Problem ist, dass dies 'GetTheValueForId' für * alle * Bezeichner sperrt. Ich denke, dass das OP mehrere gleichzeitige Ausführungen für verschiedene Identifikatoren haben möchte. –

+0

-1: Dies hilft, sobald der lang andauernde Vorgang abgeschlossen ist und der neue Wert gespeichert wird, aber bis dahin gesperrt wird. –

0

ich, dass Fälle der Mutex als:

object GetCachedValue(string Key) 
{ 
    // note here that I use the key as the name of the mutex 
    // also here you need to check that the key have no invalid charater 
    // to used as mutex name. 
    var mut = new Mutex(true, key); 

    try 
    { 
     // Wait until it is safe to enter. 
     mut.WaitOne(); 

     // here you create your cache 
     if (!Cache.ContainsKey(Key)) 
     { 
      //long running operation to fetch the value for id 
      object value = GetTheValueForId(Key); 
      Cache.Add(Key, value); 
     }  

     return Cache[Key];   
    } 
    finally 
    { 
     // Release the Mutex. 
     mut.ReleaseMutex(); 
    } 
} 

Hinweise:

  • Einige Zeichen sind für den Mutex Name ist nicht gültig (wie dem Schrägstrich)
  • Wenn der Cache für jede Anwendung (oder Web-Pool) unterschiedlich ist, und wenn wir für den Cache von asp.net sprechen, dann sperrt der Mutex alle Threads und Pools im Computer, in diesem Fall auch ich Verwenden Sie eine statische zufällige Ganzzahl, die ich dem Schlüssel hinzufüge, und nicht die Sperre für jeden Schlüssel, sondern auch für jeden Pool.
0

In diesem Fall würde Ich mag Schnittstelle haben, wie dies

using (SyncDispatcher.Enter(id)) 
{ 
    //any code here... 
} 

so konnte ich keinen Code ausführen und es wäre sicher den Thread wenn id gleich ist. Wenn ich Wert von Cache bekommen muss, bekomme ich es direkt, da es gerade keine Nebenläufigkeitsanrufe gibt.

Meine Implementierung für SyncDispatcher ist dies:

public class SyncDispatcher : IDisposable 
{ 
    private static object _lock = new object(); 
    private static Dictionary<object, SyncDispatcher> _container = new Dictionary<object, SyncDispatcher>(); 

    private AutoResetEvent _syncEvent = new AutoResetEvent(true); 

    private SyncDispatcher() { } 

    private void Lock() 
    { 
     _syncEvent.WaitOne(); 
    } 

    public void Dispose() 
    { 
     _syncEvent.Set(); 
    } 

    public static SyncDispatcher Enter(object obj) 
    { 
     var objDispatcher = GetSyncDispatcher(obj); 
     objDispatcher.Lock(); 

     return objDispatcher; 
    } 

    private static SyncDispatcher GetSyncDispatcher(object obj) 
    { 
     lock (_lock) 
     { 
      if (!_container.ContainsKey(obj)) 
      { 
       _container.Add(obj, new SyncDispatcher()); 
      } 

      return _container[obj]; 
     } 
    } 
} 

Einfacher Test:

static void Main(string[] args) 
{ 
    new Thread(() => Execute("1", 1000, "Resource 1")).Start(); 
    new Thread(() => Execute("2", 200, "Resource 2")).Start(); 
    new Thread(() => Execute("1", 0, "Resource 1 again")).Start(); 
} 

static void Execute(object id, int timeout, string message) 
{ 
    using (SyncDispatcher.Enter(id)) 
    { 
     Thread.Sleep(timeout); 

     Console.WriteLine(message);    
    } 
} 

enter image description here

Verwandte Themen