2012-01-17 8 views
12

Ho un'applicazione in cui desidero che più thread legga un elenco. Voglio aggiornare periodicamente la lista con i nuovi dati. Quando la lista viene aggiornata, immagino di poter creare una nuova lista e sostituirla con quella vecchia. Esempio:C# È necessario il blocco durante lo scambio di riferimenti variabili nell'applicazione multithreading

private List<string> _list = new List<string>(); 
private void UpdateList() 
{ 
    var newList = new List<string>(QueryList(...)); 
    _list = newList; 
} 

private void ThreadRun() 
{ 
    foreach (var item in _list) 
    { 
     // process item... 
    } 
} 

Nel metodo UpdateList, un nuovo elenco viene creato e il riferimento _list viene scambiato con il nuovo elenco. Dal mio pensiero, qualsiasi thread esistente manterrà ancora il riferimento alla vecchia lista (che è OK per me), ogni nuovo thread prenderà la nuova lista. Alla fine, tutti i thread terminano e alla fine il vecchio elenco verrà raccolto. C'è qualche blocco richiesto in questo codice, o c'è qualcosa di cui mi devo occupare per garantire un accesso multi-thread sicuro?

+0

Cosa fa 'QueryList'? Gli elementi di '_list' sono stati modificati da un thread diverso (aggiunto o rimosso)? – Groo

+0

dQueryList restituisce solo una raccolta di stringhe. Ho volutamente mostrato che è stata creata una nuova lista, per mostrare che la lista non è mai stata modificata. – Mas

risposta

6

Per essere sicuri che nèzza nè ottimizzazioni ti feriscano, utilizzare Interlocked.Exchange() per aggiornare il campo. Quindi avrai una barriera di memoria appropriata sulla scrittura, senza la spesa di avere volatile su ogni lettura.

+0

Non ho considerato l'utilizzo di Interlocked.Exchange(). Stai dicendo che è più veloce dell'usare volatile? – Mas

+0

Potresti elaborarlo un po '? Per quanto ne so, tutti i metodi 'Interlocked' generano (piene) barriere di memoria. In altre parole, sono più lenti, ma più sicuri di "volatili". – Groo

+1

@Groo Sono più lenti di una scrittura volatile, ma dal momento che le letture superano le scritture nello scenario dato avendo una scrittura da Interlocked e le letture normali sarebbero più veloci in generale rispetto ad avere tutte le letture e le scritture siano volatili. –

3

assegnazione è atomico, il codice va bene. Si può prendere in considerazione la marcatura _list come volatile se per garantire che qualsiasi thread che richiedono la variabile ottengono il maggior numero di versione aggiornata: casi

private volatile List<string> _list = new List<string>(); 
4

che stai facendo della lista immutabile, ed è solo stato mutevole che causa problemi.

Il codice è corretto, ma è consigliabile prendere in considerazione la marcatura _list come volatile. Ciò significherebbe che tutte le letture ottengono l'ultima versione e il compilatore/jitter/CPU non ottimizzerà le letture.

In alternativa, è possibile inserire uno Thread.MemoryBarrier() appena prima di assegnare il nuovo elenco per assicurarsi che tutte le scritture sul nuovo elenco vengano confermate prima di pubblicarlo, ma non si tratta di un problema sull'architettura x86.

+0

Se è volatile, a cosa serve la barriera di memoria? –

+0

Non penso che _list debba essere volatile nel caso, poiché viene letto una volta all'inizio di ThreadRun(), se è stato letto in un loop e il nuovo valore era voluto, quindi avrebbe dovuto essere volatile per smettere di essere incassato in un registro dal compilatore. –

+0

@ JonHanna, IanRingrose grazie per i vostri commenti - Ho aggiornato la mia risposta. –

3

Se si utilizza .NET 4 o + è possibile utilizzare le nuove collezioni sicure nuova discussione ...

BlockingCollection<string> list = new BlockingCollection<string>(); 

La classe BlockingCollection ha metodi thread-safe per l'aggiunta e la rimozione di membri, un po 'come il Consumer Producer design pattern.

I thread possono aggiungere e altri thread possono rimuovere membri della lista senza sovraccarico di programmazione.

Inoltre, permette di fare cose come questa ...

foreach (string i in list) 
    { 
     list.Take(); 
     list.Add(i + 200); 
    } 

Questo codice rimuove e si aggiunge ad una collezione mentre la sua enumeratore sta lavorando, qualcosa che non potrebbe mai essere sviluppato in C# prima del .NET 4. Non vi è alcuna necessità aggiuntiva di dichiararlo volatile.

foreach (string i in list) 
    { 
     new Task(() =>list.Take()).Start(); 
     new Task(() =>list.Add(i + 200)).Start(); 
    } 

In questo frammento di codice, N * 2 fili vengono iniziato che tutti operano sulla stessa lista ...

Il comportamento diverso implicita nell'uso Concurrnt Collezioni può ovviare alla necessità per voi di avere due liste.

+0

Questo ha comunque un comportamento completamente diverso. –

+0

http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx –

+0

Comportamento completamente diverso. –

3

È necessario leggere il.modello di memoria netta, come in generale l'ordine dei diritti non è definito su un processore, quindi un nuovo valore di _list può essere scritto nella memoria condivisa, mentre parte di un nuovo oggetto creato puntato da _list è ancora in una coda di scrittura dei processori.

È difficile pensare al riordino dei diritti, quindi inserisco un Thread.MemoryBarrier(); come questo.

private void UpdateList() 
{  
    var newList = new List<string>(QueryList(...)); 
    Thread.MemoryBarrier(); 
    _list = newList; 
} 

Vedere la Threading in C# web page by Joseph Albahari per maggiori dettagli

Tuttavia penso che nella maggior parte dei processori "normali" il codice funzionerà come writen.

Problemi correlati