Nos casos em que as leituras superam em número as gravações ou (por mais freqüentes) que sejam, não são simultâneas , uma cópia na gravação abordagem de pode ser apropriada.
A implementação mostrada abaixo é
- sem fechadura
- incrivelmente rápido para leituras simultâneas , mesmo enquanto modificações simultâneas estão em andamento - não importa quanto tempo elas levem
- porque "snapshots" são imutáveis, a atomicidade sem bloqueio é possível, ou seja
var snap = _list; snap[snap.Count - 1];
, nunca (bem, exceto por uma lista vazia, é claro) será lançada, e você também terá uma enumeração segura de segmentos com semântica de snapshots de graça .. como EU AMO a imutabilidade!
- implementado genericamente , aplicável a qualquer estrutura de dados e qualquer tipo de modificação
- simples morto , fácil de testar, depurar, verificar lendo o código
- utilizável no .Net 3.5
Para que a cópia na gravação funcione, é necessário manter suas estruturas de dados efetivamente imutáveis , ou seja, ninguém poderá alterá-las depois de disponibilizá-las para outros threads. Quando você deseja modificar, você
- clonar a estrutura
- faça modificações no clone
- trocar atomicamente na referência ao clone modificado
Código
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 você precisar de mais desempenho, ajudará a não gerar o método, por exemplo, crie um método para cada tipo de modificação (Adicionar, Remover, ...) desejado e codifique os ponteiros de função cloner
e op
.
NB # 1 É de sua responsabilidade garantir que ninguém modifique a estrutura de dados (supostamente) imutável. Não há nada que possamos fazer em uma implementação genérica para impedir isso, mas ao se especializar List<T>
, você pode se proteger contra modificações usando List.AsReadOnly ()
Nota # 2 Tenha cuidado com os valores da lista. A abordagem de cópia na gravação acima protege apenas a participação na lista, mas se você não colocar strings, mas alguns outros objetos mutáveis, precisará cuidar da segurança do encadeamento (por exemplo, bloqueio). Mas isso é ortogonal a esta solução e, por exemplo, o bloqueio dos valores mutáveis pode ser facilmente usado sem problemas. Você só precisa estar ciente disso.
Nota # 3 Se sua estrutura de dados é enorme e você a modifica com frequência, a abordagem de copiar tudo na gravação pode ser proibitiva em termos de consumo de memória e no custo de cópia da CPU envolvido. Nesse caso, convém usar as coleções imutáveis da MS .