Dans les cas où les lectures sont beaucoup plus nombreuses que les écritures ou (même si elles sont fréquentes) les écritures ne sont pas simultanées , une approche de copie sur écriture peut être appropriée.
L'implémentation illustrée ci-dessous est
- sans verrou
- incroyablement rapide pour les lectures simultanées , même si les modifications simultanées sont en cours - peu importe le temps qu'elles prennent
- parce que les "instantanés" sont immuables, une atomicité sans verrou est possible, c'est-à-dire
var snap = _list; snap[snap.Count - 1];
qu'elle ne lancera jamais (enfin, sauf pour une liste vide bien sûr), et vous obtenez également une énumération thread-safe avec la sémantique des instantanés gratuitement .. comment j'aime l'immuabilité!
- implémenté de manière générique , applicable à toute structure de données et tout type de modification
- mort simple , c'est-à-dire facile à tester, à déboguer, à vérifier en lisant le code
- utilisable dans .Net 3.5
Pour que la copie sur écriture fonctionne, vous devez conserver vos structures de données effectivement immuables , c'est-à-dire que personne n'est autorisé à les modifier après les avoir mises à la disposition d'autres threads. Lorsque vous souhaitez modifier, vous
- cloner la structure
- faire des modifications sur le clone
- permuter atomiquement dans la référence au clone modifié
Code
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;
}
}
}
Usage
CopyOnWriteSwapper.Swap(ref _myList,
orig => new List<string>(orig),
clone => clone.Add("asdf"));
Si vous avez besoin de plus de performances, cela aidera à dégénérer la méthode, par exemple créer une méthode pour chaque type de modification (Ajouter, Supprimer, ...) que vous voulez, et coder en dur les pointeurs de fonction cloner
et op
.
NB # 1 Il est de votre responsabilité de vous assurer que personne ne modifie la structure de données (supposée) immuable. Il n'y a rien que nous puissions faire dans une implémentation générique pour empêcher cela, mais lors de la spécialisation List<T>
, vous pouvez vous prémunir contre les modifications à l'aide de List.AsReadOnly ()
NB # 2 Faites attention aux valeurs de la liste. L'approche de copie en écriture ci-dessus protège uniquement leur appartenance à la liste, mais si vous ne mettez pas de chaînes, mais d'autres objets modifiables, vous devez prendre soin de la sécurité des threads (par exemple, le verrouillage). Mais cela est orthogonal à cette solution et par exemple le verrouillage des valeurs mutables peut être facilement utilisé sans problèmes. Vous devez juste en être conscient.
NB # 3 Si votre structure de données est énorme et que vous la modifiez fréquemment, l'approche copier-tout-sur-écriture peut être prohibitive à la fois en termes de consommation de mémoire et de coût CPU de copie. Dans ce cas, vous pouvez utiliser à la place les collections immuables de MS .