2010-08-09 9 views
7

Sono confuso da un elenco di codici in un libro che sto leggendo, C# 3 in poche parole, sul threading. Nella sezione sulla sicurezza del filetto in Application Server, sotto il codice è dato come esempio di un UserCache:Perché bloccare durante la lettura da un dizionario

static class UserCache 
{ 
    static Dictionary< int,User> _users = new Dictionary< int, User>(); 

    internal static User GetUser(int id) 
    { 
     User u = null; 

     lock (_users) // Why lock this??? 
      if (_users.TryGetValue(id, out u)) 
       return u; 

     u = RetrieveUser(id); //Method to retrieve from databse 

     lock (_users) _users[id] = u; //Why lock this??? 
      return u; 
    } 
} 

Gli autori spiegano il motivo per cui il metodo RetrieveUser non si trova in un blocco, questo è quello di evitare di bloccare la cache per un periodo più lungo.
Sono confuso sul motivo per cui blocco TryGetValue e l'aggiornamento del dizionario poiché anche con quanto sopra il dizionario viene aggiornato due volte se 2 thread chiamano contemporaneamente con lo stesso ID non recuperato.

Cosa si ottiene bloccando la lettura del dizionario?
Molte grazie in anticipo per tutti i vostri commenti e approfondimenti.

risposta

14

La classe Dictionary<TKey, TValue>is not threadsafe.

Se un thread scrive una chiave nel dizionario mentre un thread diverso legge il dizionario, potrebbe essere incasinato. (Ad esempio, se l'operazione di scrittura attiva un ridimensionamento dell'array o se le due chiavi sono una collisione hash)

Pertanto, il codice utilizza un blocco per impedire le scritture simultanee.

+0

Grazie! Ciò ha senso. Immagino che non ci sia davvero alcun modo di dimostrarlo in modo deterministico. Ho provato a rimuovere i blocchi e l'ho chiamato in un ciclo per alcune migliaia di volte creando nuovi thread, dopo aver modificato il codice in modo che ogni thread scrivesse sempre dopo la lettura, ma ha funzionato correttamente. Ma come con la maggior parte dei threading, il fatto che ha funzionato per me non significa che funzionerà sempre. Saluti! – eastender

+0

Devo ancora bloccarlo quando il dizionario viene impostato una volta per thread singolo e viene letto solo da più thread in seguito? Probabilmente no, ma un'altra opinione sarà utile – Adassko

+0

No; più letture vanno bene. – SLaks

1

Questa è una pratica comune per accedere a qualsiasi non filo strutture sicure come le liste, dizionari, valori condivisi comuni, ecc

E rispondendo alla domanda principale: il blocco di una lettura vi possiamo garantire che dizionario non verrà modificato da un altro thread, mentre stiamo leggendo il suo valore. Questo non è implementato nel dizionario ed è per questo che è chiamato non thread safe :)

4

C'è una condizione di razza benigna quando scrive nel dizionario; è possibile, come hai affermato, che due thread determinino che non c'è una voce corrispondente nella cache. In questo caso, entrambi leggeranno dal DB e quindi tentano di inserirlo. Viene mantenuto solo l'oggetto inserito dall'ultimo thread; l'altro oggetto sarà garbage collection quando il primo thread è fatto con esso.

leggere nel dizionario deve essere bloccato perché un altro thread può scrivere nello stesso momento e la lettura deve eseguire la ricerca su una struttura coerente.

Si noti che il ConcurrentDictionary introdotto in .NET 4.0 sostituisce praticamente questo tipo di linguaggio.

0

Se due thread chiamano contemporaneamente e l'ID esiste, restituiranno entrambe le informazioni Utente corrette. Il primo blocco serve a prevenire errori come ha detto SLaks: se qualcuno sta scrivendo sul dizionario mentre stai cercando di leggerlo, avrai dei problemi. In questo scenario, il secondo blocco non verrà mai raggiunto.

Se due thread chiamano contemporaneamente e l'ID non esiste, un thread si bloccherà e inserirà TryGetValue, questo restituirà false e imposterà un valore predefinito. Questo primo blocco è di nuovo, per prevenire gli errori descritti da SLaks. A questo punto, quel primo thread rilascerà il blocco e il secondo thread entrerà e farà lo stesso. Entrambi quindi imposteranno 'u' alle informazioni da 'RetrieveUser (id)'; questa dovrebbe essere la stessa informazione. Un thread bloccherà quindi il dizionario e assegnerà _users [id] al valore di u. Questo secondo blocco è così che due thread stanno cercando di scrivere valori nelle stesse posizioni di memoria simultaneamente e corrompendo quella memoria. Non so cosa farà il secondo thread quando entrerà nel compito.Restituirà presto ignorando l'aggiornamento o sovrascrivendo i dati esistenti dal primo thread. Indipendentemente da ciò, il dizionario conterrà le stesse informazioni perché entrambi i thread dovrebbero aver ricevuto gli stessi dati in "u" da RetrieveUser.

Per prestazioni, l'auther ha confrontato due scenari: lo scenario sopra descritto, che sarà estremamente raro e bloccato mentre due thread tentano e scrivono gli stessi dati, e il secondo in cui è molto più probabile che due thread chiamino nella richiesta di dati per un oggetto che ha bisogno di essere scritto e uno che esiste. Ad esempio, threadA e threadB chiamano contemporaneamente e blocchi ThreadA per un ID che non esiste. Non c'è motivo di rendere threadB attendere una ricerca mentre threadA sta lavorando su RetriveUser. Probabilmente questa situazione è molto più probabile degli id ​​duplicati descritti sopra, quindi per le prestazioni l'autore ha scelto di non bloccare l'intero blocco.

Problemi correlati