2016-05-04 10 views
1

Ich teste einfache Caching-Logik in C#. Hier ist meine Cachemanager-Klasse:sollte ich lokale Variable in Multithreading sperren?

public class CacheManager 
{ 
    static List<string> _list = new List<string>(); 
    static readonly object _syncobject = new object(); 

    public static void ReloadList() 
    { 
     List<string> list = new List<string>(); 

     Random r = new Random(); 
     var count = r.Next(10); 

     for (int i = 0; i < count; i++) 
     { 
      list.Add("list" + i); 
     } 

     //lock (_syncobject) 
     { 
      _list = list; 
     } 

    } 


    public static IEnumerable<string> GetList() 
    { 
     //lock (_syncobject) 
     { 
      return _list; 
     } 
    } 

} 

Unten ist die Klasse, die viele Threads raubend Cachemanager laicht:

class Program 
{ 
    static void Main(string[] args) 
    { 

     //threads for re-loading the list 
     for (int i = 0; i < 3; i++) 
     { 
      Thread reloadThread = new Thread(ReloadList); 
      reloadThread.Start(); 
     } 

     //threads for getting the list 
     for (int i = 0; i < 10; i++) 
     { 
      Thread tget = new Thread(PrintList); 
      tget.Start(); 
     } 

     //threads for getting the list and storing in local variable then use it 
     for (int i = 0; i < 10; i++) 
     { 
      Thread tget = new Thread(PrintListWithLocalVariable); 
      tget.Start(); 
     } 

     Console.ReadKey(); 

    } 

    private static void ReloadList() 
    { 
     do 
     { 
      Console.WriteLine("Reloading **********"); 
      CacheManager.ReloadList(); 

     } while (true); 
    } 

    private static void PrintList() 
    { 
     do 
     { 
      foreach (var item in CacheManager.GetList()) 
      { 
       if (item == null) 
        throw new Exception("i == null"); 

       Console.WriteLine(item); 
      } 

     } while (true); 
    } 

    private static void PrintListWithLocalVariable() 
    { 
     do 
     { 
      var list = CacheManager.GetList(); 
      foreach (var listitem in list) 
      { 
       var i = list.FirstOrDefault(x => x.Equals(listitem)); 
       if (i == null) 
        throw new Exception("i == null"); 
       Console.WriteLine("Printing with Local variable:" + listitem); 
      } 

     } while (true); 
    } 


} 

Mein Verständnis war, dass wir die _list Variable in Cachemanager sperren sollte, aber sieht nicht wie wir brauche das. Ich habe den obigen Test für eine Stunde oder so ausgeführt, aber habe keinen Fehler bekommen. Während ReloadThread die zufällige Anzahl von Listenelementen neu lädt, andere Threads, die die Liste durchlaufen, dachte ich, könnte Probleme haben. Kann mir jemand erklären, warum das Programm ohne Probleme läuft?

Danke.

+0

Müssen Sie Ihren eigenen Cache erfinden? https://msdn.microsoft.com/en-us/library/system.runtime.caching.memorycache(v=vs.110).aspx. Versuchen Sie außerdem, eine ConcurrentCollection zu verwenden. https://msdn.microsoft.com/en-us/library/dd997305(v=vs.110).aspx –

+0

FYI, '_list' ist keine" lokale "Variable. – crashmstr

+2

Die Erklärung ist: weil du Glück hast. Sie können Glück haben, die nächsten 100 Male, die Sie dieses laufen lassen. Aber es wird dich umdrehen und an unbehaglichen Stellen beißen, sobald du es brauchst, um richtig zu arbeiten. – nvoigt

Antwort

2

_list ist eine statische Variable, was bedeutet, dass jede Instanz von CacheManager dieselbe Instanz von _list teilt. Der Zugriff auf _list sollte in der Tat gesperrt werden, um Nebenläufigkeitsprobleme zu vermeiden. Wie jemand in einem Kommentar erwähnt, sollte auch ein ConcurrentCollection statt List(of T) verwendet werden, da List(of T) nicht Thread-sicher ist.

+0

In der Tat habe ich in meiner Produktionslogik ein Schloss hinzugefügt, aber vorher habe ich es getestet und ohne Sperren lief die Logik für einige Zeit gut und das brachte mich dazu, diese Frage zu posten, ob mir irgendetwas fehlt. ConcurrentCollection ist hier möglicherweise nicht nützlich, da ich keine Elemente aus Listen hinzufüge und abrufe. In der ReloadList Logik setze ich einfach die Klassenvariable mit Funktionsvariablen. Und während ich zurückkomme, gebe ich IEnumerable zurück, das, glaube ich, eine Kopie für einen Verbraucher erstellt. – Santosh

+0

Wenn Sie den Rückgabetyp auf IEnumerable (von T) festlegen, wird keine Kopie von _list erstellt. Es gibt es nur als weniger abgeleiteten Typ zurück. Wenn Sie eine Kopie von '_list 'zurückgeben möchten, können Sie den Getter-Code in' return _list.ToArray(); 'ändern, wodurch die Liste (aber nicht die darin enthaltenen Objekte) kopiert wird. Eine bessere Vorgehensweise könnte darin bestehen, sie als schreibgeschützt mit 'return _list.ToList() zurückzugeben. AsReadOnly();' (ToList() kopiert auch die ursprüngliche Liste). – DVK