En los casos en que las lecturas superan en número a las escrituras, o (aunque sean frecuentes) las escrituras no son concurrentes , una copia en escritura enfoque de puede ser apropiado.
La implementación que se muestra a continuación es
- sin cerradura
- increíblemente rápido para lecturas concurrentes , incluso mientras las modificaciones concurrentes están en curso, sin importar cuánto tiempo demoren
- debido a que las "instantáneas" son inmutables, la atomicidad sin bloqueo es posible, es decir
var snap = _list; snap[snap.Count - 1];
, nunca se lanzará (bueno, excepto por una lista vacía, por supuesto), y también obtendrá una enumeración segura de subprocesos con semántica de instantáneas gratis ... ¡cómo ME ENCANTA la inmutabilidad!
- implementado genéricamente , aplicable a cualquier estructura de datos y cualquier tipo de modificación
- muy simple , es decir, fácil de probar, depurar, verificar leyendo el código
- utilizable en .Net 3.5
Para que la copia en escritura funcione, debe mantener sus estructuras de datos efectivamente inmutables , es decir, a nadie se le permite cambiarlas después de ponerlas a disposición de otros hilos. Cuando quieres modificar, tú
- clonar la estructura
- hacer modificaciones en el clon
- intercambiar atómicamente la referencia al clon 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"));
Si necesita más rendimiento, ayudará a desgenerizar el método, por ejemplo, crear un método para cada tipo de modificación (Agregar, Eliminar, ...) que desee y codificar los punteros de función cloner
y op
.
NB # 1 Es su responsabilidad asegurarse de que nadie modifique la estructura de datos (supuestamente) inmutable. No hay nada que podamos hacer en una implementación genérica para evitar eso, pero cuando se especialice List<T>
, podría protegerse contra modificaciones utilizando List.AsReadOnly ()
NB # 2 Tenga cuidado con los valores en la lista. El enfoque de copia en escritura anterior protege solo su pertenencia a la lista, pero si no pone cadenas, sino algunos otros objetos mutables, debe tener cuidado de la seguridad del hilo (por ejemplo, bloqueo). Pero eso es ortogonal a esta solución y, por ejemplo, el bloqueo de los valores mutables se puede usar fácilmente sin problemas. Solo necesitas estar consciente de ello.
NB # 3 Si su estructura de datos es enorme y la modifica con frecuencia, el enfoque de copiar todo en escritura podría ser prohibitivo tanto en términos de consumo de memoria como del costo de copia de la CPU involucrado. En ese caso, es posible que desee utilizar las colecciones inmutables de MS en su lugar.