Nei casi in cui le letture superano di molto le scritture o le scritture (per quanto frequenti) non siano simultanee , può essere appropriato un approccio di copia su scrittura .
L'implementazione mostrata di seguito è
- lockless
- incredibilmente veloce per letture simultanee , anche mentre sono in corso modifiche simultanee - non importa quanto tempo impiegano
- perché gli "snapshot" sono immutabili, l'atomicità senza blocco è possibile, cioè
var snap = _list; snap[snap.Count - 1];
non verrà mai (beh, tranne per un elenco vuoto ovviamente), e otterrai anche un elenco thread-safe con semantica di snapshot gratis .. come ADORO l'immutabilità!
- implementato genericamente , applicabile a qualsiasi struttura di dati e qualsiasi tipo di modifica
- semplice , cioè facile da testare, eseguire il debug, verificare leggendo il codice
- utilizzabile in .Net 3.5
Affinché copy-on-write funzioni, è necessario mantenere le strutture dei dati effettivamente immutabili , ovvero nessuno è autorizzato a modificarle dopo averle rese disponibili per altri thread. Quando vuoi modificare, tu
- clonare la struttura
- apportare modifiche al clone
- scambiare atomicamente il riferimento al clone modificato
Codice
static class CopyOnWriteSwapper
{
public static void Swap<T>(ref T obj, Func<T, T> cloner, Action<T> op)
where T : class
{
while (true)
{
var objBefore = Volatile.Read(ref obj);
var newObj = cloner(objBefore);
op(newObj);
if (Interlocked.CompareExchange(ref obj, newObj, objBefore) == objBefore)
return;
}
}
}
uso
CopyOnWriteSwapper.Swap(ref _myList,
orig => new List<string>(orig),
clone => clone.Add("asdf"));
Se hai bisogno di maggiori prestazioni, ti aiuterà a non rigenerare il metodo, ad es. Crea un metodo per ogni tipo di modifica (Aggiungi, Rimuovi, ...) che desideri, e codifica i puntatori di funzione cloner
e op
.
NB # 1 È responsabilità dell'utente assicurarsi che nessuno modifichi la struttura (presumibilmente) immutabile dei dati. Non c'è nulla che possiamo fare in un'implementazione generica per evitarlo , ma quando ti specializzi a List<T>
, potresti proteggerti dalle modifiche usando List.AsReadOnly ()
NB # 2 Prestare attenzione ai valori nell'elenco. L'approccio copy on write sopra protegge solo l'appartenenza all'elenco, ma se non inserissi stringhe, ma alcuni altri oggetti mutabili, devi occuparti della sicurezza del thread (ad esempio il blocco). Ma questo è ortogonale a questa soluzione e, ad esempio, il blocco dei valori mutabili può essere facilmente utilizzato senza problemi. Devi solo esserne consapevole.
NB # 3 Se la struttura dei dati è enorme e la si modifica frequentemente, l'approccio copia su scrittura potrebbe essere proibitivo sia in termini di consumo di memoria che di costo della CPU per la copia. In tal caso, potresti voler utilizzare invece le raccolte immutabili di MS .