2009-08-27 8 views
17

Ich schreibe eine C# -Anwendung. Ich habe (eine Art) Logging-Klasse. und diese Protokollierungsklasse wird von vielen Threads verwendet. Wie macht man diese Klasse Thread sicher? Soll ich es als Singleton machen? Was sind die besten Praktiken dort? Gibt es ein Dokument, das ich lesen kann, um es threadsicher zu machen?So erstellen Sie eine Klasse Thread Safe

Dank

+7

Das Singleton-Muster impliziert keine Thread-Sicherheit. – dtb

+2

Beginnen Sie damit, sorgfältig zu definieren, was Sie mit "thread safe" meinen. Die Leute benutzen diesen Begriff so, als ob er etwas Spezifisches bedeutet, obwohl es in Wirklichkeit bedeutet, dass es "in Szenario X korrekt funktioniert". Ohne eine Spezifikation für "richtig" und eine Aussage darüber, was X ist, kannst du nicht wirklich etwas implementieren und wissen, dass du ein Problem gelöst hast, das du wirklich hast. –

+1

Schauen Sie sich [diesen tollen Artikel] (http://www.albahari.com/threading/part2.aspx#_ThreadSafety) von Joseph Albahari an. Etwa halb in den Artikel ist ein großer Abschnitt über "Thread Safety" –

Antwort

15

In C# kann jedes Objekt verwendet werden, um einen "kritischen Abschnitt" zu schützen, mit anderen Worten, Code, der nicht von zwei Threads gleichzeitig ausgeführt werden muss.

Im folgenden Beispiel wird der Zugriff auf die SharedLogger.Write-Methode synchronisiert, sodass nur ein einzelner Thread eine Nachricht zu einem bestimmten Zeitpunkt protokolliert.

public class SharedLogger : ILogger 
{ 
    public static SharedLogger Instance = new SharedLogger(); 

    public void Write(string s) 
    { 
     lock (_lock) 
     { 
     _writer.Write(s); 
     } 
    } 

    private SharedLogger() 
    { 
     _writer = new LogWriter(); 
    } 

    private object _lock; 
    private LogWriter _writer; 
} 
+0

Der obige Code ist ein gutes Beispiel. – Maciek

+0

Das ist gut, aber Sie können einfach _lock und _writer statisch machen, anstatt sich damit zu beschäftigen, dieses Ding zu einem Singleton zu machen. Die Verwendung eines vorhandenen Loggers wäre jedoch immer noch viel einfacher. –

+3

Ich stimme der Verwendung einer vorhandenen Loggerimplementierung zu, aber wenn es sich um Multithread-Code handelt, muss er eventuell wissen, wie man den Zugriff auf verschiedene Ressourcen richtig synchronisiert, und dieser Prozess kann auch anderswo angewendet werden ... – jeremyalan

3

Verwendung lock() so dass mehrere Threads nicht die Protokollierung zugleich

6
  • versuchen und tun den meisten Berechnung unter Verwendung von lokalen Variablen verwenden und dann den Zustand des Objekts verändern, in einem schnellen lock Ed-Block.
  • Denken Sie daran, dass sich einige Variablen ändern können, wenn Sie sie lesen und wenn Sie den Status ändern.
+0

+1 für die zweite Linie im Hinterkopf, ich verschwendete 2 Wochen Verständnis dafür, dass –

9

Ich würde einen Logger verwenden, da es mehrere gibt, die solide und einfach zu bedienen sind. Keine Notwendigkeit, Ihre eigenen zu rollen. Ich empfehle Log4Net.

+0

Log4Net ist großartig, ich habe es mehrmals verwendet und war immer glücklich mit es. –

+10

Dem stimme ich in Bezug auf die Protokollierung zu. Aber es beantwortet die Frage nicht. –

+0

Log4Net ist der Shiznit! – Crackerjack

2

Nach BCS‘Antwort:

BCS den Fall eines staatenlos Objekt beschreibt. Solch ein Objekt ist an sich Thread-sicher, da es keine eigenen Variablen besitzt, die durch Aufrufe von verschiedenen Theads verfälscht werden können.

Der beschriebene Logger hat einen File-Handle (sorry, kein C# -Benutzer, vielleicht heißt er IDiskFileResource oder ein solcher MS-ism), zu dem er serialisieren muss.

Trennen Sie also die Speicherung von Nachrichten von der Logik, die sie in die Protokolldatei schreibt. Die Logik sollte immer nur eine Nachricht gleichzeitig verarbeiten.

Eine Möglichkeit ist: Wenn das Logger-Objekt eine Warteschlange von Nachrichtenobjekten behalten soll und das Logger-Objekt lediglich die Logik hat, eine Nachricht aus der Warteschlange zu löschen, dann extrahieren Sie das nützliche Material aus einem Nachrichtenobjekt, Schreiben Sie das dann in das Protokoll und suchen Sie nach einer anderen Nachricht in der Warteschlange. Dann können Sie diesen Thread sicherer machen, indem Sie die Operationen add/remove/queue_size/etc der Warteschlange sicher machen. Es würde die Logger-Klasse, eine Nachrichtenklasse und eine Thread-sichere Warteschlange erfordern (was wahrscheinlich eine dritte Klasse ist, deren Instanz eine Mitgliedsvariable der Logger-Klasse ist).

+0

Eine weitere Option (in einigen Fällen) besteht darin, dass der Protokollierer den vollständigen Datensatz im lokalen Speicher generiert und ihn dann in einem einzigen Aufruf in den Ausgabestream schreibt. IIRC die meisten Betriebssysteme bieten einen Systemaufruf, der einen atomaren Schreibvorgang für einen Strom für (fast?) Jeden Fall aber Ressourcen Erschöpfung machen wird, und dann haben Sie andere Probleme. – BCS

8

Ich bin nicht sicher, ob ich etwas hinzufügen kann, was bereits gesagt wurde, um eine Protokollierungsklasse threadsicher zu machen. Wie bereits erwähnt, müssen Sie dazu den Zugriff auf die Ressource, d. H. Die Protokolldatei, synchronisieren, so dass nur ein Thread versucht, sich gleichzeitig an der Ressource anzumelden. Das C# lock-Schlüsselwort ist der richtige Weg, dies zu tun.

Ich werde jedoch (1) die Singleton-Ansatz und (2) die Benutzerfreundlichkeit des Ansatzes, die Sie letztlich entscheiden, zu verwenden.

(1) Wenn Ihre Anwendung alle Protokollmeldungen in eine einzige Protokolldatei schreibt, ist das Singleton-Muster definitiv die Route, die es zu gehen gilt. Die Protokolldatei wird beim Start geöffnet und beim Herunterfahren geschlossen, und das Singleton-Muster passt perfekt zu diesem Betriebskonzept. Wie @ dtb schon sagte, erinnern Sie sich daran, dass das Gewährleisten einer Singleton-Klasse die Thread-Sicherheit nicht garantiert. Verwenden Sie dafür das Schlüsselwort lock.

(2) Was die Brauchbarkeit des Ansatzes betrachten diese vorgeschlagene Lösung:

public class SharedLogger : ILogger 
{ 
    public static SharedLogger Instance = new SharedLogger(); 
    public void Write(string s) 
    { 
     lock (_lock) 
     { 
     _writer.Write(s); 
     } 
    } 
    private SharedLogger() 
    { 
     _writer = new LogWriter(); 
    } 
    private object _lock; 
    private LogWriter _writer; 
} 

Lassen Sie mich zunächst sagen, dass dieser Ansatz im Allgemeinen in Ordnung ist. Es definiert eine Singleton-Instanz von SharedLogger über die statische Variable Instance und verhindert, dass andere die Klasse aufgrund des privaten Konstruktors instanziieren. Dies ist die Essenz des Singleton-Musters, aber ich empfehle dringend, Jon Skeets Rat bezüglich singletons in C# zu lesen und zu befolgen, bevor Sie zu weit gehen.

Allerdings möchte ich auf die Benutzerfreundlichkeit dieser Lösung konzentrieren. Mit "Usability" beziehe ich mich darauf, wie man diese Implementierung verwenden würde, um eine Nachricht zu protokollieren. Überlegen Sie, was der Aufruf wie folgt aussieht:

SharedLogger.Instance.Write("log message"); 

Das ganze ‚Instanz‘ Teil sieht einfach falsch, aber es gibt keine Möglichkeit sie die Umsetzung gegeben zu vermeiden. Stattdessen betrachten diese Alternative:

public static class SharedLogger : ILogger 
{ 
    private static LogWriter _writer = new LogWriter(); 
    private static object _lock = new object(); 
    public static void Write(string s) 
    { 
     lock (_lock) 
     { 
      _writer.Write(s); 
     } 
    } 
} 

Beachten Sie, dass die Klasse jetzt statisch ist, was bedeutet, dass alle ihre Mitglieder und Methoden statisch sein. Es unterscheidet sich nicht wesentlich von dem vorherigen Beispiel, aber beachte seine Verwendung.

SharedLogger.Write("log message"); 

Das ist viel einfacher zu codieren.

Es geht nicht darum, die frühere Lösung zu verunglimpfen, sondern zu empfehlen, dass die Verwendbarkeit der von Ihnen gewählten Lösung ein wichtiger Aspekt ist, der nicht zu übersehen ist. Eine gute, verwendbare API kann den Code einfacher schreiben, eleganter und einfacher zu verwalten.

+0

Dem stimme ich zu ... Viel weniger Unsinn. –

+0

Statische Klassen, wo immer möglich, als Faustregel. –

+0

können wir nicht einfach lock (this) statt lock (_this) verwenden? (Verwenden von Instant als das Objekt zu sperren?) –

1

Meiner Meinung nach ist der obige Code nicht mehr threadsicher: In der vorherigen Lösung musste man ein neues Objekt von SharedLogger instatieren und die Methode Write existierte für jedes Objekt einmal.

Jetzt hat man nur eine Write-Methode, die von allen Threads verwendet wird, beispielsweise:

Gewinde 1: SharedLogger.Write ("Thread 1")

Gewinde 2: SharedLogger.Schreibe ("Faden 2");

public static void Write(string s) 
    { 
     // thread 1 is interrupted here <= 
     lock (_lock) 
     { 
      _writer.Write(s); 
     } 
    } 
  • Gewinde 1 möchte eine Nachricht schreiben, wird jedoch durch Gewinde 2 (siehe Kommentar)
  • Gewinde 2 überschreibt die Nachricht des Threads 1 unterbrochen und wird durch Thread unterbrochen 1

  • Thread 1 bekommt die Sperre und schreibt "Thread 2"

  • Thread 1 gibt das Schloss frei
  • Thread 2 bekommt die Sperre und schreibt "Thread 2"
  • Thread 2 die Sperre frei

mir korrigieren, wenn ich falsch bin ...

0

Wenn die Leistung nicht‘das Hauptproblem, zum Beispiel, wenn die Klasse nicht unter viel der Last, dies nur tun:

machen Sie Ihre Klasse Context

Wenden Sie dieses Attribut auf Ihre Klasse erben [Synchronisation]

Ihre gesamte Klasse ist jetzt nur für jeweils einen Thread zugänglich.

Es ist wirklich nützlich für die Diagnose, wie Geschwindigkeit ist es fast der schlimmste Fall ... aber schnell zu bestimmen "ist dieses seltsame Problem ein Rennzustand", werfen Sie es auf, führen Sie den Test erneut .. wenn das Problem geht weg ... Sie wissen, dass es ein Threading-Problem ist ...

Eine performantere Option besteht darin, dass Ihre Protokollierungsklasse eine threadsichere Nachrichtenwarteschlange hat (die Loggingnachrichten akzeptiert, dann einfach herauszieht und sequenziell verarbeitet). die ConcurrentQueue Klasse in den neuen Parallelität Sachen.

Zum Beispiel eine guten Thread-sicher-Warteschlange ist.

Oder Benutzer log4net RollingLogFileAppender, der bereits thread safe ist.