Differenza tra serratura (armadietto) e serratura (variabile_quando_I_am_uso)

Sto usando C # e .NEt 3.5. Qual è la differenza tra OptionA e OptionB?

class MyClass { private object m_Locker = new object(); private Dicionary m_Hash = new Dictionary(); public void OptionA() { lock(m_Locker){ // Do something with the dictionary } } public void OptionB() { lock(m_Hash){ // Do something with the dictionary } } } 

Sto iniziando a dilettarmi nel threading (principalmente per creare una cache per un’app multi-thread, NON usando la class HttpCache, dato che non è collegata a un sito web), e vedo la syntax OptionA in molti degli esempi I vedere online, ma non capisco che cosa, se del caso, motivo viene fatto su OptionB.

L’opzione B utilizza l’object da proteggere per creare una sezione critica. In alcuni casi, questo comunica più chiaramente l’intento. Se utilizzato in modo coerente, garantisce solo una sezione critica per l’object protetto che sarà attivo alla volta:

 lock (m_Hash) { // Across all threads, I can be in one and only one of these two blocks // Do something with the dictionary } lock (m_Hash) { // Across all threads, I can be in one and only one of these two blocks // Do something with the dictionary } 

L’opzione A è meno restrittiva. Usa un object secondario per creare una sezione critica per l’object da proteggere. Se vengono utilizzati più oggetti secondari, è ansible avere più di una sezione critica per l’object protetto alla volta.

 private object m_LockerA = new object(); private object m_LockerB = new object(); lock (m_LockerA) { // It's possible this block is active in one thread // while the block below is active in another // Do something with the dictionary } lock (m_LockerB) { // It's possible this block is active in one thread // while the block above is active in another // Do something with the dictionary } 

L’opzione A è equivalente all’opzione B se si utilizza solo un object secondario. Per quanto riguarda il codice di lettura, l’intento di Option B è più chiaro. Se stai proteggendo più di un object, l’opzione B non è davvero un’opzione.

È importante capire che il blocco (m_Hash) NON impedisce ad altri codici di utilizzare l’hash. Impedisce solo l’esecuzione di altri codici che utilizza anche m_Hash come object di blocco.

Un motivo per utilizzare l’opzione A è perché è probabile che le classi abbiano variabili private che userete all’interno dell’istruzione lock. È molto più semplice usare solo un object che usi per bloccare l’accesso a tutti loro invece di provare a utilizzare blocchi di grana più fine per bloccare l’accesso solo ai membri di cui avrai bisogno. Se si tenta di utilizzare il metodo con grana più fine, in alcune situazioni è probabile che si debbano prendere più lucchetti e quindi è necessario assicurarsi di averli sempre adottati nello stesso ordine per evitare situazioni di stallo.

Un altro motivo per utilizzare l’opzione A è perché è ansible che il riferimento a m_Hash sia accessibile al di fuori della class. Forse hai una proprietà pubblica che ti fornisce l’accesso, o forse la dichiari come classi protette e derivate possono usarlo. In entrambi i casi, una volta che il codice esterno ha un riferimento ad esso, è ansible che il codice esterno lo utilizzi per un blocco. Questo apre anche la possibilità di deadlock dal momento che non hai modo di controllare o sapere quale ordine verrà preso il lucchetto.

In realtà, non è una buona idea bloccare l’object se si stanno utilizzando i suoi membri. Jeffrey Richter ha scritto nel suo libro “CLR via C #” che non vi è alcuna garanzia che una class di oggetti che si sta utilizzando per la sincronizzazione non utilizzi lock(this) nella sua implementazione (è interessante, ma era un modo consigliato per la sincronizzazione Microsoft per qualche tempo … Poi, hanno scoperto che si trattava di un errore), quindi è sempre una buona idea usare uno speciale object separato per la sincronizzazione. Quindi, come puoi vedere OptionB non ti darà una garanzia di stallo – sicurezza. Quindi OptionA è molto più sicuro di OptionB.

Non è quello che stai “Locking”, è il codice che è contenuto tra il lock {…} che è importante e che stai impedendo di essere eseguito.

Se un thread estrae un lock () su qualsiasi object, impedisce ad altri thread di ottenere un blocco sullo stesso object e quindi impedisce al secondo thread di eseguire il codice tra le parentesi.

Ecco perché la maggior parte delle persone crea solo un object indesiderato da bloccare, impedisce ad altri thread di ottenere un blocco sullo stesso object indesiderato.

Penso che l’ambito della variabile che “passi” determinerà l’ambito della serratura. cioè una variabile di istanza sarà rispetto all’istanza della class mentre una variabile statica sarà per l’intero AppDomain.

Osservando l’implementazione delle collezioni (usando Reflector), il modello sembra seguire che una variabile di istanza chiamata SyncRoot è dichiarata e utilizzata per tutte le operazioni di blocco rispetto all’istanza della raccolta.

Bene, dipende da cosa si desidera bloccare (essere reso sicuro da un thread).

Normalmente sceglierei OptionB per fornire SOLO l’accesso a thread-safe a m_Hash. Dove come OptionA, avrei usato per il tipo di valore di blocco, che non può essere utilizzato con il blocco, o ho avuto un gruppo di oggetti che devono essere bloccati contemporaneamente, ma non so cosa bloccare l’intera istanza usando il lock(this)

Bloccare l’object che stai usando è semplicemente una questione di convenienza. Un object lock esterno può semplificare le cose, ed è anche necessario se la risorsa condivisa è privata, come con una collezione (nel qual caso si utilizza l’object ICollection.SyncRoot ).

OptionA è il modo per andare qui finché tutto il tuo codice, quando accedi a m_hash, usi m_Locker per bloccarlo.

Ora immagina questo caso. Tu blocchi l’object. E quell’object in una delle funzioni chiamate ha un segmento di codice di lock(this) . In questo caso si tratta di una situazione di stallo sicuro irrecuperabile