2012-01-11 7 views
9

Sono stato a conoscenza del blocco sui thread e non ho trovato una spiegazione per perché la creazione di un tipico System.Object, il suo blocco e l'esecuzione di qualsiasi azione richiesta durante il blocco fornisce la sicurezza del thread?Perché la magia fa bloccare un'istanza di System.Object in modo diverso rispetto al blocco di un tipo di istanza specifico?

Esempio

object obj = new object() 
lock (obj) { 
    //code here 
} 

In un primo momento ho pensato che fosse solo di essere utilizzato come un segnaposto negli esempi e pensato per essere scambiato con il tipo che si sta trattando. Ma trovo che esempi come Dennis Phillips punti, non sembra essere qualcosa di diverso rispetto all'utilizzo di un'istanza di Object.

Quindi, prendendo un esempio di dover aggiornare un dizionario privato, ciò che il blocco un'istanza di System.Object fare per fornire la sicurezza dei thread in contrasto con il blocco in realtà il dizionario (lo so il blocco del dizionario in questo caso potrebbe tra maiuscole e problemi di sincronizzazione)? E se il dizionario fosse pubblico?

//what if this was public? 
private Dictionary<string, string> someDict = new Dictionary<string, string>(); 

var obj = new Object(); 
lock (obj) { 
    //do something with the dictionary 
} 
+0

Se sei preoccupato che il 'dizionario' sia pubblico, scrivi (o copia) un wrapper' Dictionary' racchiude tutte le funzionalità in un 'lock'. Vedi [Qual è il modo migliore di implementare un dizionario thread-safe in .NET?] (Http://stackoverflow.com/questions/157933) per esempi e spiegazioni. O semplicemente usa un ['ConcurrentDictionary'] (http://msdn.microsoft.com/en-us/library/dd287191.aspx) – Brian

risposta

8

E 'usato per essere pratica comune per bloccare sui dati condivisi in sé:

private Dictionary<string, string> someDict = new Dictionary<string, string>(); 

lock (someDict) 
{ 
    //do something with the dictionary 
} 

Ma la (un po' teorico) obiezione è che altro codice, al di fuori del vostro controllo, potrebbe anche bloccare il someDict e quindi potresti avere un punto morto.

Quindi si consiglia di utilizzare un oggetto (molto) privato, dichiarato in corrispondenza 1 a 1 con i dati, da utilizzare come supporto per il blocco. Finché tutto il codice che accede al dizionario si blocca su obj la sicurezza del battistrada è garantita.

// the following 2 lines belong together!! 
private Dictionary<string, string> someDict = new Dictionary<string, string>(); 
private object obj = new Object(); 


// multiple code segments like this 
lock (obj) 
{ 
    //do something with the dictionary 
} 

Quindi lo scopo di obj è di agire come un proxy per il dizionario, e sin dalla sua tipo non importa usiamo il tipo più semplice, System.Object.

E se il dizionario fosse pubblico?

Quindi tutte le scommesse sono disattivate, qualsiasi codice può accedere al dizionario e il codice al di fuori della classe contenente non è nemmeno in grado di bloccare l'oggetto guard. E prima di iniziare a cercare le correzioni, questo semplicemente non è un modello sostenibile. Utilizzare un ConcurrentDictionary o mantenere un normale privato.

+0

+1 per menzionare che l'altro codice potrebbe usare blocchi di cui non sei a conoscenza. Come il Dizionario che si blocca su se stesso internamente. –

+0

Inserendo l'istanza dell'oggetto a livello di membro dell'istanza, blocca l'intera classe? – pghtech

+0

Inoltre, sembra che si possano ancora ottenere deadlock al livello del metodo in cui ci si sta bloccando su obj se due thread stanno tentando di chiamare un metodo. Se sono corretto, c'è un modo per gestire la richiesta a livello di metodo? Ti piace una coda di richieste? – pghtech

2

L'oggetto utilizzato per il blocco non si trova in relazione agli oggetti modificati durante il blocco. Potrebbe essere qualsiasi cosa, ma dovrebbe essere privato e senza stringa, poiché gli oggetti pubblici potrebbero essere modificati esternamente e le stringhe potrebbero essere utilizzate da due blocchi per errore.

9

Lo stesso lock non fornisce alcuna sicurezza per il tipo Dictionary<TKey, TValue>. Che lock fa è essenzialmente

Nell'uso dei lock(objInstance) solo filo sarà mai nel corpo della dichiarazione lock per un oggetto (objInstance)

Se ogni utilizzo di un determinato Dictionary<TKey, TValue> l'istanza si verifica all'interno di un lock.E ognuno di quelli lock utilizza lo stesso oggetto, quindi si sa che solo un thread alla volta accede/modifica il dizionario. Questo è fondamentale per impedire a più thread di leggere e scrivere allo stesso tempo e di corrompere il suo stato interno.

C'è un problema enorme con questo approccio però: è necessario assicurarsi che ogni utilizzo del dizionario si verifica all'interno di un lucchetto e utilizza lo stesso oggetto. Se ne dimentichi anche una, hai creato una potenziale condizione di competizione, non ci saranno avvisi del compilatore e probabilmente il bug rimarrà sconosciuto per qualche tempo.

Nel secondo esempio è stato mostrato che si sta utilizzando un'istanza di oggetto locale (var indica un metodo locale) come parametro di blocco per un campo oggetto. Questa è quasi certamente la cosa sbagliata da fare. Il locale vivrà solo per tutta la durata del metodo. Quindi 2 chiamate al metodo useranno il lock su differenti locals e quindi tutti i metodi saranno in grado di entrare simultaneamente nel lock.

1

Per quanto ho capito, l'uso di un generico object è semplicemente quello di avere qualcosa da bloccare (come un oggetto bloccato internamente). Per spiegarlo meglio; Supponiamo che tu abbia due metodi all'interno di una classe, entrambi accedono allo Dictionary, ma potrebbero essere in esecuzione su thread diversi. Per impedire a entrambi i metodi di modificare contemporaneamente lo Dictionary (e potenzialmente causare il deadlock), è possibile bloccare alcuni oggetti per controllare il flusso. Questo è meglio illustrata dal seguente esempio:

private readonly object mLock = new object(); 

public void FirstMethod() 
{ 
    while (/* Running some operations */) 
    { 
     // Get the lock 
     lock (mLock) 
     { 
      // Add to the dictionary 
      mSomeDictionary.Add("Key", "Value"); 
     } 
    } 
} 

public void SecondMethod() 
{ 
    while (/* Running some operation */) 
    { 
     // Get the lock 
     lock (mLock) 
     { 
      // Remove from dictionary 
      mSomeDictionary.Remove("Key"); 
     } 
    } 
} 

L'uso dell'istruzione lock(...) in entrambi i metodi sullo stesso oggetto impedisce i due metodi di accedere alla risorsa allo stesso tempo.

0

Le regole importanti per l'oggetto che si blocca su sono:

  1. Si deve essere un oggetto visibile solo per il codice che ha bisogno di bloccarlo su di esso. Questo evita che altri codici si blocchino anche su di esso.

    1. Questo esclude le stringhe che potrebbero essere internate e gli oggetti Type.
    2. Questo esclude this nella maggior parte dei casi e le eccezioni sono troppo poche e offrono poco sfruttamento, quindi non utilizzare this.
    3. Si noti inoltre che alcuni casi interni al blocco del framework su Type s e this, così mentre "va bene fino a quando nessun altro lo fa" è vero, ma è già troppo tardi.
  2. Deve essere statico per proteggere le operazioni statiche statiche, può essere un'istanza per proteggere le operazioni di istanza (comprese quelle interne a un'istanza che è contenuta in una statica).

  3. Non si desidera bloccare un tipo di valore. Se anche tu volessi davvero, potresti bloccare un particolare boxing di esso, ma non riesco a pensare a qualcosa che questo potrebbe ottenere oltre a dimostrare che è tecnicamente possibile - porterà comunque al codice che è meno chiaro su quali serrature su cosa.

  4. Non si desidera bloccare un campo che è possibile modificare durante il blocco in attesa, in quanto non si avrà più il blocco su quello che sembra avere il blocco (è semplicemente plausibile che non ci sia un uso pratico per l'effetto di questo, ma ci sarà un'impedenza tra ciò che il codice sembra fare in prima lettura e ciò che realmente fa, che non è mai buono).

  5. Lo stesso oggetto deve essere utilizzato per bloccare tutte le operazioni che potrebbero essere in conflitto tra loro.

  6. Mentre si può avere la correttezza con serrature troppo larghe, è possibile ottenere prestazioni migliori con più fine. Per esempio. se avessi un lucchetto che proteggeva 6 operazioni, e hai capito che 2 di quelle operazioni non potevano interferire con le altre 4, quindi hai cambiato 2 oggetti di blocco, quindi puoi ottenere una migliore coerenza (o crash-and- bruciano se hai sbagliato in questa analisi!)

Il primo punto esclude di blocco su tutto ciò che è o visibile o che potrebbe essere reso visibile (ad esempio un'istanza privata che viene restituito da un membro protetto o pubblico dovrebbe essere considerato pubblico per quanto riguarda questa analisi, qualsiasi cosa catturata da un delegato potrebbe finire altrove, e così via).

Gli ultimi due punti possono significare che non esiste un "tipo di oggetto" evidente, perché i blocchi non proteggono gli oggetti, le operazioni di protezione eseguite sugli oggetti e si può avere più di un oggetto interessato o lo stesso oggetto interessato da più di un gruppo di operazioni che devono essere bloccate.

Quindi può essere una buona pratica avere un oggetto che esiste puramente per bloccare. Dal momento che non fa altro, non può essere confuso con altre semantiche o scritto sopra quando non te lo aspetti. E poiché non fa nient'altro, potrebbe anche essere il tipo di riferimento più leggero che esiste in .NET; System.Object.

Personalmente, preferisco bloccare un oggetto relativo a un'operazione quando si adatta chiaramente alla distinta del "tipo con cui si ha a che fare", e nessuna delle altre preoccupazioni si applica, come mi sembra essere abbastanza autodocumentante, ma per gli altri il rischio di sbagliare supera questo vantaggio.

Problemi correlati