2012-01-19 18 views
5

So che è sbagliato usare lock(this) o qualsiasi oggetto condiviso.Il thread di utilizzo del blocco è sicuro?

Mi chiedo se questo utilizzo è OK?

public class A 
{ 
    private readonly object locker = new object(); 
    private List<int> myList; 
    public A() 
    { 
    myList = new List<int>() 
    } 

    private void MethodeA() 
    { 
    lock(locker) 
    { 
     myList.Add(10); 
    } 
    } 

    public void MethodeB() 
    { 
    CallToMethodInOtherClass(myList); 
    } 
} 

public class OtherClass 
{ 
    private readonly object locker = new object(); 
    public CallToMethodInOtherClass(List<int> list) 
    { 
    lock(locker) 
    { 
    int i = list.Count; 
    } 
    } 
} 

Questa thread è sicura? In OtherClass ci blocco con un oggetto privato, quindi se il blocco class A con il suo blocco privato può ancora cambiare la lista nel blocco di blocco in OtherClass?

+11

Il tuo bagno ha due porte, ognuna con un lucchetto. La tua domanda è "supponiamo che io blocchi solo il primo lucchetto quando sono sotto la doccia e il mio amico Bob blocca solo il secondo lucchetto quando è sotto la doccia, potremmo mai finire entrambi nella doccia contemporaneamente?" Ovviamente sì! Se tu e Bob volete evitare di fare la doccia insieme, dovete accettare entrambi di usare * lo stesso lucchetto *. Non è possibile rendere l'accesso a un oggetto sicuro come questo. –

risposta

10

No, non è sicuro da thread. Add e Count possono essere eseguiti nello stesso "tempo". Hai due diversi oggetti di blocco.

bloccare sempre il proprio oggetto di blocco quando passa la lista:

public void MethodeB() 
    { 
    lock(locker) 
    { 
     CallToMethodInOtherClass(myList); 
    } 
    } 
+0

In realtà è thread-safe, anche se solo come un dettaglio di implementazione di 'Count' che è thread-safe che non è promesso, e non in un modo che si estenderebbe bene in un codice più realistico. –

2

No, non è thread-safe.

I 2 metodi bloccati su 2 oggetti diversi, non si bloccheranno reciprocamente.

Poiché CallToMethodInOtherClass() recupera solo il valore di Count, nulla andrà terribilmente storto. Ma il lock() attorno ad esso è inutile e fuorviante.

Se il metodo dovesse apportare modifiche nell'elenco, si avrebbe un brutto problema. Per risolverlo, modificare MethodeB:

public void MethodeB() 
    { 
    lock(locker) // same instance as MethodA is using 
    { 
     CallToMethodInOtherClass(myList); 
    } 
    } 
+0

quello che pensavo. così puoi dirmi come la strada giusta? – Maya

2

No, devono bloccare lo stesso oggetto. Con il tuo codice entrambi bloccano un altro e ogni chiamata può essere eseguita simultaneamente.

Per rendere sicuro il thread di codice, inserire un blocco in MethodeB o utilizzare l'elenco stesso come oggetto di blocco.

+0

@ Felix Ho pensato che fosse sbagliato bloccare con l'oggetto condiviso, quindi bloccare con la lista stessa è sbagliato? – Maya

+0

@Maya È vero, è sbagliato. Ma è un modo semplice per risolvere il problema descritto. Ma comunque il modo giusto sarebbe quello di creare un elenco sicuro da thread o un contenitore che contiene l'elenco e accedere all'elenco solo attraverso il contenitore. –

3

No, questo non è thread-safe. A.MethodeA e OtherClass.CallToMethodInOtherClass si bloccano su oggetti diversi, quindi non si escludono a vicenda. Se è necessario proteggere l'accesso alla lista, non passare a codice esterno, tenerlo privato.

0

Probabilmente il modo più semplice per fare il trucco

public class A 
{ 
    private List<int> myList; 
    public A() 
    { 
    myList = new List<int>() 
    } 

    private void MethodeA() 
    { 
    lock(myList) 
    { 
     myList.Add(10); 
    } 
    } 

    public void MethodeB() 
    { 
    CallToMethodInOtherClass(myList); 
    } 
} 

public class OtherClass 
{ 
    public CallToMethodInOtherClass(List<int> list) 
    { 
    lock(list) 
    { 
    int i = list.Count; 
    } 
    } 
} 
+0

ma ho pensato che il suo wront per bloccare con oggetto shraed – Maya

1

Ass tutte le risposte dicono che questi sono diversi oggetti di blocco.

un modo semplice è quello di avere un f.ex oggetto di blocco statico:

publc class A 
{ 
    public static readonly object lockObj = new object(); 
} 

e in entrambe le classi utilizzare il blocco come:

lock(A.lockObj) 
{ 
} 
3

No questo non è thread sicuro. Per renderlo thread-safe è possibile utilizzare il blocco sugli oggetti static perché sono condivisi tra i thread, questo potrebbe causare deadlock nel codice ma può essere gestito mantenendo l'ordine corretto per il blocco. C'è un costo di prestazioni associato a lock quindi usarlo con saggezza.

Spero che questo aiuti

+0

come questo mi aiuti? – Maya

+0

grazie, ora capisco – Maya

+0

Gli oggetti statici sono condivisi tra altri oggetti, quindi se si mette il blocco su questo, sarà consumato da un solo oggetto alla volta. Maggiori informazioni su [statico su MSDN] (http://msdn.microsoft.com/en-us/library/79b3xss3 (v = vs80) .aspx) o [lock su MSDN] (http://msdn.microsoft .com/it/us/library/c5kehkcz (v = vs.80) .aspx) (_Il best practice è definire un oggetto privato da bloccare o una variabile oggetto statica privata per proteggere i dati comuni a tutte le istanze._ MSDN) –

0

Molte delle risposte hanno menzionato utilizzando un blocco di sola lettura statica.

Tuttavia, si dovrebbe davvero cercare di evitare questo blocco statico.Sarebbe facile creare un deadlock in cui più thread stanno usando il blocco statico.

Ciò che è possibile utilizzare è una delle collezioni simultanee .net 4, che forniscono una sincronizzazione dei thread per conto dell'utente, in modo che non sia necessario utilizzare il blocco.

Dai uno sguardo allo spazio dei nomi System.collections.Concurrent. Per questo esempio, è possibile utilizzare la classe ConcurrentBag<T>.

1

realtà è thread-safe (semplicemente come una questione di un particolare implementazione su Count), ma:

  1. frammenti thread-safe di codice, non un thread-safe applicazione make. È possibile combinare diverse operazioni thread-safe in operazioni non thread-safe. In effetti, molti codici non thread-safe possono essere scomposti in pezzi più piccoli, che sono tutti thread-safe da soli.

  2. Non è thread-safe per il motivo che speravi, il che significa che estenderlo ulteriormente non sarebbe thread-safe.

Questo codice sarebbe thread-safe:

public void CallToMethodInOtherClass(List<int> list) 
{ 
    //note we've no locks! 
    int i = list.Count; 
    //do something with i but don't touch list again. 
} 

chiamata con qualsiasi elenco, e si metterà a dare i un valore in base allo stato di tale elenco, indipendentemente da ciò che altri thread sono fino a. Non corrompe list. Non darà a i un valore non valido.

Così, mentre questo codice è thread-safe:

public void CallToMethodInOtherClass(List<int> list) 
{ 
    Console.WriteLine(list[93]); // obviously only works if there's at least 94 items 
          // but that's nothing to do with thread-safety 
} 

Questo codice non sarebbe thread-safe:

public void CallToMethodInOtherClass(List<int> list) 
{ 
    lock(locker)//same as in the question, different locker to that used elsewhere. 
    { 
    int i = list.Count; 
    if(i > 93) 
     Console.WriteLine(list[93]); 
    } 
} 

Prima di proseguire, i due bit sono descritti come filettatura la sicurezza non è promessa dalle specifiche per l'elenco. La codifica conservativa presuppone che non siano thread-safe anziché dipendere dai dettagli dell'implementazione, ma dipenderò dai dettagli di implementazione perché influisce sulla domanda su come utilizzare i lock in un modo importante:

Perché c'è codice operativo su list che non sta acquisendo il blocco su locker per prima cosa, tale codice non viene impedito di essere eseguito contemporaneamente a CallToMethodInOtherClass. Ora, mentre list.Count è thread-safe e list[93] è sicuro, * la combinazione dei due in cui dipendiamo dal primo per garantire che il secondo non sia thread-safe. Poiché il codice al di fuori del blocco può influire su list, è possibile che il codice chiami Remove o Clear tra Count assicurandoci che list[93] funzionerebbe e list[93] chiamato.

Ora, se sappiamo che list viene aggiunto solo a, va bene, anche se un ridimensionamento sta accadendo contemporaneamente finiremo con il valore di list[93] in entrambi i casi. Se qualcosa sta scrivendo su list[93] ed è un tipo che .NET scrive a livello atomico (e lo int è uno di questi tipi), finiremo con quello vecchio o quello nuovo, proprio come se avessimo bloccato correttamente noi 'Ottenere il vecchio o il nuovo a seconda di quale thread andare il blocco prima.Anche in questo caso, questo è un dettaglio di implementazione che non è una promessa specifica, lo sto affermando solo per sottolineare come la sicurezza del thread fornita abbia ancora come risultato un codice non thread-safe.

Spostare questo verso il codice reale. Non dovremmo dare per scontato che list.Count e list[93] siano infallibili perché non ci avevano promesso che sarebbero stati cambiati, ma anche se avessimo avuto quella promessa, quelle due promesse non si sarebbero aggiunte alla promessa che sarebbero state thread-safe insieme.

L'importante è utilizzare lo stesso blocco per proteggere i blocchi di codice che possono interferire l'uno con l'altro. Quindi, si consideri la variante di sotto che è garantito essere threadsafe:

public class ThreadSafeList 
{ 
    private readonly object locker = new object(); 
    private List<int> myList = new List<int>(); 

    public void Add(int item) 
    { 
    lock(locker) 
     myList.Add(item); 
    } 
    public void Clear() 
    { 
    lock(locker) 
     myList.Clear(); 
    } 
    public int Count 
    { 
    lock(locker) 
     return myList.Count; 
    } 
    public int Item(int index) 
    { 
    lock(locker) 
     return myList[index]; 
    } 
} 

Questa classe è garantito essere threadsafe in ogni sua attività. Senza dipendere da alcun dettaglio di implementazione, qui non esiste alcun metodo che possa corrompere lo stato o fornire risultati errati a causa di ciò che un altro thread sta facendo con la stessa istanza. Il seguente codice ancora non funziona però:

// (l is a ThreadSafeList visible to multiple threads. 
if(l.Count > 0) 
    Console.WriteLine(l[0]); 

Abbiamo garantito il filo di sicurezza di ogni chiamata al 100%, ma non abbiamo garantito la combinazione, e noi non ci riesce garanzia la combinazione.

Ci sono due cose che possiamo fare. Possiamo aggiungere un metodo per la combinazione. Qualcosa come la seguente sarebbe comune per molte classi specificamente progettati per uso multi-threaded:

public bool TryGetItem(int index, out int value) 
{ 
    lock(locker) 
    { 
    if(l.Count > index) 
    { 
     value = l[index]; 
     return true; 
    } 
    value = 0; 
    return false; 
    } 
} 

Questo rende il test conteggio e la parte elemento recupero di una singola operazione che è garantito per essere thread-safe.

Alternativamente, e più spesso ciò che dobbiamo fare, abbiamo capita la serratura nel luogo in cui sono raggruppate le operazioni:

lock(lockerOnL)//used by every other piece of code operating on l 
    if(l.Count > 0) 
    Console.WriteLine(l[0]); 

Naturalmente, questo rende le serrature all'interno ThreadSafeList ridondante e solo uno spreco di sforzo, spazio e tempo. Questo è il motivo principale per cui la maggior parte delle classi non fornisce thread-safety sui membri delle istanze - dal momento che non è possibile proteggere in modo significativo gruppi di chiamate sui membri all'interno della classe, è una perdita di tempo provare a meno che le promesse sulla sicurezza dei thread sono molto ben specificati e utili da soli.

Per tornare al codice nella tua domanda:

Il blocco in CallToMethodInOtherClass dovrebbe essere rimosso a meno che OtherClass ha la sua ragione di bloccaggio internamente. Non può fare una promessa significativa che non verrà combinato in modo non sicuro e aggiungere più blocchi a un programma aumenta la complessità dell'analisi per assicurarsi che non ci siano deadlock.

La chiamata a CallToMethodInOtherClass deve essere protetto dalla stessa serratura come altre operazioni in quella classe:

public void MethodeB() 
{ 
    lock(locker) 
    CallToMethodInOtherClass(myList); 
} 

Poi, finché CallToMethodInOtherClass non memorizza myList da qualche parte che può essere visto da altri thread in seguito, non importa che CallToMethodInOtherClass non sia thread-safe perché l'unico codice che può accedere a myList porta la propria garanzia di non chiamarlo in concomitanza con altre operazioni su myList.

Le due cose importanti sono:

  1. Quando qualcosa è descritto come "thread-safe", so solo ciò che è promettente che, in quanto vi sono diversi tipi di promessa che cadono sotto "thread-safe "e da solo significa solo" non metterò questo oggetto in uno stato senza senso ", che sebbene sia un elemento importante, non è molto da solo.

  2. Lock gruppi delle operazioni, con lo stesso blocco per ogni gruppo che ti influenzano gli stessi dati, e la guardia l'accesso agli oggetti in modo che non ci può eventualmente essere un altro thread non giocare a palla con questo.

* Questa è una definizione molto limitata di thread-safe. Chiamando list[93] su un List<T> dove T è un tipo che verrà scritto e letto in modo atomico e non sappiamo se in realtà ha almeno 94 elementi ugualmente sicuri indipendentemente dal fatto che ci siano altri thread operativi su di esso. Ovviamente, il fatto che sia in grado di lanciare ArgumentOutOfRangeException in entrambi i casi non è ciò che la maggior parte delle persone considererebbe "sicuro", ma la garanzia che abbiamo con più thread rimane la stessa di una. È che otteniamo una garanzia più forte controllando Count in un singolo thread ma non in una situazione multi-thread che mi porta a descriverlo come non thread-safe; mentre quella combo continua a non corrompere lo stato, può portare a un'eccezione che ci saremmo assicurati che non potesse accadere.

+0

Usa ConcurrentBag o ConcurrentDictionary invece, quindi non dovrai preoccuparti del blocco. –

+0

@ PålThingbø Non è vero nel minimo. Per prima cosa mentre quelle collezioni, e in effetti il ​​mio [dizionario e set di thread-safe] (https://bitbucket.org/JonHanna/ariadne) e alcune altre tali collezioni, sono thread-safe per ogni singola azione su di loro, gruppi di azioni su di essi non sono necessariamente thread-safe per i motivi che ho fornito sopra (pensateci, 'int' è thread-safe, ma questo non rende' ++ x' thread-safe, perché le tre azioni thread-safe che comporta non sono thread-safe come unità). C'è anche raro punto che suggerisca a qualcuno di usare una borsa o un dizionario quando vogliono una lista; il ... –

+0

@ PålThingbø la semantica di quelle classi è completamente diversa. Spero di rilasciare presto una classe di elenco thread-safe che fornisce un comportamento concorrente molto migliore di quello nella risposta di cui sopra, anche se sarà ancora limitato (non 'RemoveAt' o' Insert'; potrei produrre una classe diversa che li ha troppo tardi, ma renderle thread-safe senza il blocco è molto più ingombrante del resto), e ancora non lo renderà il codice usandolo magicamente thread-safe nel suo complesso, più di quanto non faccia "ConcurrentDictionary", sempre per i motivi Io do sopra. –

Problemi correlati