2010-08-19 10 views
8

Eventuali duplicati:
Are C# auto-implemented static properties thread-safe?Sta ottenendo e impostando un filetto di proprietà statiche semplice sicuro?

Nella seguente classe esempio

static class Shared 
{ 
    public static string[] Values { get; set; } 
} 

molti fili lettore leggere la matrice Values stringa periodicamente, mentre di volta in volta un singolo scrittore sostituirà l'intero array con un nuovo valore usando il setter. Devo usare un ReaderWriterLock o C# è qualcosa che gestirà automaticamente?

Modifica: nel mio caso l'unica richiesta "sicurezza del thread" è: Niente di brutto dovrebbe accadere quando il writer sostituisce l'array mentre il lettore sta cercando un valore. Non mi interessa se i lettori useranno il nuovo valore immediatamente fintanto che lo useranno in futuro

+2

Duplicato di http://stackoverflow.com/questions/2074670/are-c-auto-implemented-static-properties-thread-safe? –

+0

Controlla lo spazio dei nomi 'System.Collections.Concurrent' se sei in .NET 4. A seconda del tuo utilizzo, possono soddisfare anche le tue esigenze. – Marc

risposta

10

Questo uso è thread-safe:

string[] localValues = Shared.Values; 
for (int index = 0; index < localValues.length; index++) 
    ProcessValues(localValues[index]); 

Questo uso non è thread-safe, e può causare eccezioni out-of-bounds:

for (int index = 0; index < Shared.Values.Length; index++) 
    ProcessValues(Shared.Values[index]); 

Io preferirei fare thread sicuro chiama più naturale facendo qualcosa del genere:

static class Shared 
{ 
    private static string[] values; 
    public static string[] GetValues() { return values; } 
    public static void SetValues(string[] values) { Shared.values = values; } 
} 

Ovviamente, gli utenti possono ancora inserire GetValues ​​() nei loop, e sarebbe altrettanto male, ma almeno è ovviamente negativo.

A seconda della situazione, una soluzione ancora migliore potrebbe essere quella di distribuire copie, in modo che il codice chiamante non possa mutare affatto l'array. Questo è quello che farei di solito, ma potrebbe non essere appropriato nella tua situazione.

static class Shared 
{ 
    private static string[] values; 
    public static string[] GetValues() 
    { 
     string[] currentValues = values; 
     if (currentValues != null) 
      return (string[])currentValues.Clone(); 
     else 
      return null; 
    } 
    public static void SetValues(string[] values) 
    { 
     Shared.values = values; 
    } 
} 
+2

Per aggiungere a questa risposta: non è richiesto un blocco in questo scenario di lettura lenta perché prendere un riferimento all'array condiviso funge da operazione atomica.La lettura di un riferimento (puntatore) da una variabile condivisa è thread-safe anche se un altro thread sta per scrivere un nuovo valore nella variabile condivisa. Questa è una forma di controllo delle versioni "generazionale": ogni volta che si fa riferimento ai dati condivisi, si tratta essenzialmente di un'immutabile istantanea dei dati, mantenuta in vita fino a quando non vengono rilasciati tutti i riferimenti. – dthorpe

+0

Chiarimento: le letture dei puntatori di riferimento sono atomiche a livello hardware su x86 e x64 quando l'indirizzo di memoria è dword allineato, che sono abbastanza sicuro è garantito dall'assegnatore .NET. Le letture non allineate possono richiedere più cicli di clock per leggere ciascuna metà e combinarli, e ciò crea una finestra di opportunità per una scrittura in un'altra discussione per modificare il valore in memoria prima che la lettura abbia letto la seconda metà. – dthorpe

+3

@dthorpe - L'importante è che la definizione del linguaggio C# garantisca che un riferimento a un oggetto venga copiato in un fasion atomico indipendentemente dalla piattaforma su cui è in esecuzione o da come viene implementato il meccanismo. E questa è una cosa abbastanza utile per circostanze come questa. :-) –

2

Questo non è thread-safe. Sì, dovrai usare un lucchetto.

+1

Questa risposta sarebbe molto più utile (o falsificabile) se contenesse qualche dettaglio - come i pericoli che prevedi. –

+0

"Thread-safe" e "not thread-safe" sono usati troppo liberamente. In questo caso, le letture e le scritture sono garantite per essere atomiche, ma probabilmente non in ordine. Un blocco mantiene l'ordine, ma l'ordine potrebbe non essere importante qui. –

5

Dipende da cosa intendi per thread-safe.

La lettura e la sostituzione sono garantite per essere atomiche, ma non è garantito che una lettura che segue una scrittura necessariamente leggerà il valore fresco.

In risposta alla tua modifica ...

Niente di male accadrà con il codice esistente (ad esempio, strappato letture), ma non v'è alcuna garanzia che i tuoi lettori si mai vedere il nuovo valore . Ad esempio, è possibile, anche se forse improbabile, che il vecchio riferimento venga memorizzato per sempre in un registro.

+0

Ho aggiornato la mia domanda con il livello richiesto di sicurezza del thread. – xsl

7

Senza alcuna protezione aggiuntiva, non c'è garanzia che un thread di lettura sarà mai vedere il nuovo valore. In pratica, se il thread di lettura sta facendo qualcosa di significativo, lo vedrà il nuovo valore. In particolare, non vedrai mai "metà" di un aggiornamento, con un riferimento che punta allo spazio morto.

Se si effettua il campo volatile, credo che anche questo pericolo venga rimosso, ma la programmazione senza blocco è generalmente difficile da ragionare. Questo significa abbandonare il fatto che sia una proprietà implementata automaticamente, ovviamente.

+0

@LukeH: Forse ... anche la mia comprensione è limitata. Tuttavia, credo che "più o meno" si accerti di leggere sempre l'ultimo valore. (Questa è anche la descrizione MSDN di esso.) –

1

Vorrei bloccare. Per la lettura multipla, la scrittura occasionale utilizza ReaderWriterLockSlim - molto più efficiente di ReaderWriteLock.

static class Shared 
{ 
    private static ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); 
    private static string[] _values; 

    public static string[] Values 
    { 
     get 
     { 
      _rwLock.EnterReadLock(); 
      try 
      { 
       return _values; 
      } 
      finally 
      { 
       _rwLock.ExitReadLock(); 
      } 
     } 
     set 
     { 
      _rwLock.EnterWriteLock(); 
      try 
      { 
       _values = value; 
      } 
      finally 
      { 
       _rwLock.ExitWriteLock(); 
      } 
     } 
    } 
} 
+0

Un semplice 'lock 'sarebbe ancora più veloce. Il motivo è che i lucchetti RW hanno un numero limitato di scenari in cui sono effettivamente più veloci nella pratica. Questo non è uno di loro. Ho appena fatto un punto di riferimento sulla mia macchina e un 'lock' era circa 5 volte più veloce. –

+0

Sono d'accordo Brian, un blocco è più veloce, ma se stessimo parlando di un numero elevato di thread che accedono ai dati contemporaneamente, forse questo potrebbe avere alcuni vantaggi. In termini umani la differenza è piuttosto insignificante! – Michael

1

Per andare un po 'off-topic, il modello 1.5 di memoria Java 2 aggiunge due garanzie (vedi Java theory and practice: Fixing the Java Memory Model, Part 2):

  • volatili letture/scritture sono anche barriere di memoria.
  • final i campi appaiono completamente inizializzati al di fuori del costruttore.

Quest'ultimo è un caso interessante: è stato utilizzato per essere in grado di fare foo = new String(new char[]{'a','b','c'}); in un thread e foo = "123"; System.out.println(foo) in un altro thread e stampare la stringa vuota, perché non vi era alcuna garanzia che le operazioni di scrittura ai campi finali di foo accadrà prima scrivere a foo.

Non sono sicuro dei dettagli dell'inizializzazione dell'array .NET; potrebbe essere il caso in cui è necessario utilizzare Thread.MemoryBarrier() (all'inizio del getter e alla fine del setter) per garantire che i lettori vedano solo array completamente inizializzati.

Problemi correlati