2009-08-27 10 views
17

Sto scrivendo un'applicazione C#. Ho (una sorta di) classe di registrazione. e questa classe di log verrà utilizzata da molti thread. Come rendere sicuro questo thread di classi? Devo farlo come singleton? quali sono le migliori pratiche lì? C'è un documento che posso leggere su come renderlo thread-safe?Come rendere una classe Thread Safe

Grazie

+7

Il modello singleton non implica la sicurezza del thread. – dtb

+2

Inizia definendo accuratamente cosa intendi per "thread safe". Le persone usano questo termine come se significasse qualcosa di specifico, quando in realtà significa semplicemente "funziona correttamente nello scenario X". Senza una specifica per "correttamente" e una dichiarazione su cosa sia X, non si può effettivamente implementare qualcosa e si sa che hai risolto un problema che davvero hai. –

+1

Dai un'occhiata a [questo grande articolo] (http://www.albahari.com/threading/part2.aspx#_ThreadSafety) di Joseph Albahari. Circa a metà dell'articolo è una grande sezione su "Thread Safety" –

risposta

15

In C#, qualsiasi oggetto può essere usato per proteggere una "sezione critica", o in altre parole, codice che non deve essere eseguito da due thread contemporaneamente.

Ad esempio, quanto segue sincronizzerà l'accesso al metodo SharedLogger.Write, quindi solo un singolo thread registra un messaggio in un dato momento.

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

il codice sopra è un buon esempio. – Maciek

+0

Questo è buono, ma puoi semplicemente rendere _lock e _writer statico invece di dover gestire questa cosa singleton. Utilizzare un logger esistente sarebbe comunque molto più facile. –

+3

Sono d'accordo sull'utilizzo di un'implementazione di logger esistente, ma se si occupa di codice multi-thread, alla fine dovrà sapere come sincronizzare correttamente l'accesso a varie risorse, e questo stesso processo può essere applicato altrove ... – jeremyalan

3

uso lock() modo che più thread non utilizzare la registrazione allo stesso tempo

6
  • cercare di fare più calcolo utilizzando variabili locali e quindi alterare lo stato dell'oggetto in un breve lock blocco.
  • Ricorda che alcune variabili potrebbero cambiare tra quando le leggi e quando modifichi lo stato.
+0

+1 per tenere in mente la seconda linea, ho sprecato 2 settimane di comprensione che –

9

Per questo, utilizzerei un registratore off the shelf, poiché ce ne sono diversi che sono solidi e facili da usare. Non c'è bisogno di tirare il tuo. Io raccomando Log4Net.

+0

Log4Net è fantastico, l'ho usato un certo numero di volte e sono sempre stato contento esso. –

+10

Sono d'accordo con questo per quanto riguarda la registrazione. Ma non risponde alla domanda. –

+0

Log4Net è lo shiznit! – Crackerjack

2

Conformemente risposta BCS':

BCS descrive il caso di un oggetto stateless. Un tale oggetto è intrinsecamente thread-safe perché non ha variabili proprie che possono essere riempite da chiamate provenienti da diversi punti.

Il logger descritto ha un handle di file (mi dispiace, non l'utente C#, forse si chiama IDiskFileResource o alcuni di questi MS-ism) che deve serializzare l'uso di.

Quindi, separare la memorizzazione dei messaggi dalla logica che li scrive nel file di registro. La logica dovrebbe funzionare solo su un messaggio alla volta.

Un modo per farlo è: se l'oggetto logger dovesse mantenere una coda di oggetti messaggio, e l'oggetto logger ha semplicemente la logica per far scattare un messaggio fuori dalla coda, quindi estrarre le cose utili da un oggetto messaggio, quindi scriverlo nel registro, quindi cercare un altro messaggio nella coda, quindi è possibile rendere sicuro quel thread facendo in modo che il thread di operazioni di aggiunta/rimozione/coda_size/etc della coda sia sicuro. Richiederebbe la classe logger, una classe messaggio e una coda thread-safe (che è probabilmente una terza classe, un'istanza di cui è una variabile membro della classe logger).

+0

Un'altra opzione (in alcuni casi) prevede che la chiamata del registratore generi il record completo nella memoria locale e quindi la scriva nel flusso di output in una singola chiamata. La maggior parte dei sistemi operativi IIRC fornisce una chiamata di sistema che eseguirà una scrittura atomica su un flusso per (quasi?) In ogni caso, ma esaurimento delle risorse, e quindi si avranno altri problemi. – BCS

8

Non sono sicuro di poter aggiungere nulla a ciò che è già stato detto su come rendere sicura una classe di log. Per fare ciò è necessario sincronizzare l'accesso alla risorsa, ad esempio il file di registro, in modo che solo un thread tenti di collegarsi ad esso alla volta. La parola chiave C# lock è il modo corretto per farlo.

Tuttavia, affronterò (1) l'approccio singleton e (2) l'usabilità dell'approccio che alla fine deciderete di utilizzare.

(1) Se l'applicazione scrive tutti i suoi messaggi di registro in un singolo file di registro, il pattern singleton è sicuramente la strada da percorrere. Il file di registro verrà aperto all'avvio e chiuso all'arresto e il modello singleton si adatta perfettamente a questo concetto di operazioni. Come ha sottolineato @dtb, però, ricorda che fare una classe a singleton non garantisce la sicurezza del thread. Utilizzare la parola chiave lock per quello.

(2) Per quanto riguarda l'usabilità del metodo, si consideri questa soluzione suggerita:

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

Permettetemi di dire che questo approccio è generalmente male. Definisce un'istanza singleton di SharedLogger tramite la variabile statica Instance e impedisce ad altri di creare un'istanza della classe in virtù del costruttore privato. Questa è l'essenza del modello singleton, ma consiglio vivamente di leggere e seguire il consiglio di Jon Skeet riguardante singletons in C# prima di andare troppo lontano.

Tuttavia, quello su cui voglio concentrarmi è l'usabilità di questa soluzione. Per "usabilità", mi riferisco al modo in cui si userebbe questa implementazione per registrare un messaggio. Considerate ciò che l'invocazione assomiglia:

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

che tutta la parte 'grado' sembra solo sbagliato, ma non c'è modo di evitarlo dato l'implementazione. Invece, di questa alternativa:

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

Si noti che la classe è ormai statico, il che significa che tutti i suoi membri e metodi devono essere statico. Non è sostanzialmente diverso dall'esempio precedente, ma considera il suo utilizzo.

SharedLogger.Write("log message"); 

Questo è molto più semplice da codificare.

Il punto non è denigrare la soluzione precedente, ma suggerire che l'usabilità di qualsiasi soluzione tu scelga è un aspetto importante da non trascurare. Una buona API utilizzabile può rendere il codice più semplice da scrivere, più elegante e più facile da mantenere.

+0

Sono d'accordo con questo ... Molto meno sciocchezze. –

+0

Classi statiche, ove possibile, come regola generale. –

+0

non possiamo semplicemente usare lock (this) invece di lock (_questo)? (usare l'istantaneo come oggetto da bloccare?) –

1

a mio parere il codice sopra riportato non è thread-safe più: nella soluzione precedente si doveva instaziare un nuovo oggetto di SharedLogger e il metodo Write esisteva una volta per ogni oggetto.

Ora v'è solo un metodo Write, che viene utilizzato da tutte le discussioni, ad esempio:

filo 1: SharedLogger.Write ("filettatura 1")

discussione 2: SharedLogger.Scrivi ("Thread 2");

public static void Write(string s) 
    { 
     // thread 1 is interrupted here <= 
     lock (_lock) 
     { 
      _writer.Write(s); 
     } 
    } 
  • thread 1 vuole scrivere un messaggio, ma viene interrotto da filo 2 (vedi commento)
  • filo 2 override il messaggio di filo 1 e viene interrotto da thread 1

  • il thread 1 ottiene il blocco e scrive "Thread 2"

  • thread 1 rilascia il blocco
  • thread 2 ottiene il blocco e scrive "Thread 2"
  • filo 2 rilascia il blocco

correggetemi se mi sbaglio ...

0

Se le prestazioni non e' il problema principale, per esempio se la classe non è sotto un sacco di carico, solo fare questo:

Fai la tua classe eredita ContextBoundObject

Applicare questo attributo alla classe [Synchronization]

L'intera classe è ora accessibile solo a un thread alla volta.

È davvero più utile per la diagnosi, poiché la velocità è quasi il peggiore ... ma per determinare rapidamente "questo strano problema è una condizione di competizione", lanciarlo, eseguire nuovamente il test .. se il problema va via ... sai che è un problema di threading ...

Un'opzione più performante è quella di rendere la classe di logging dotata di una coda di messaggi thread-safe (che accetta i messaggi di log, quindi li estrae e li elabora in modo sequenziale .. .

Per esempio la classe ConcurrentQueue nella nuova roba parallelismo è una coda sicura buona thread.

O utente log4net RollingLogFileAppender, che è già thread safe.