В случаях, когда чтение значительно превышает количество записей или (хотя и часто) записи выполняются не одновременно , может подойти подход копирование при записи .
Реализация, показанная ниже,
- без блокировки
- невероятно быстро для одновременных операций чтения , даже когда одновременные модификации продолжаются - независимо от того, какдолго они занимают
- , потому что "снимки" неизменны, атомарность без блокировки возможна, то есть
var snap = _list; snap[snap.Count - 1];
никогда не будет (ну, кроме, конечно, пустого списка) бросать, и вы также получитеПотоково-безопасное перечисление с семантикой снимков бесплатно .. как я ЛЮБЛЮ неизменность! - реализовано в общем , применимо к любой структуре данных и любого типа модификации
- очень просто , то есть легко тестировать, отлаживать, проверять, читая код
- можно использовать в .Net 3.5
Чтобы копирование при записи работало, вы должны хранить свои данные вtructures фактически неизменяемый , т.е. никто не может изменять их после того, как вы сделали их доступными для других потоков.Когда вы хотите изменить, вы
- клонируете структуру
- , вносите изменения в клон
- , атомарно меняете местами ссылку на модифицированный клон
Код
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;
}
}
}
Использование
CopyOnWriteSwapper.Swap(ref _myList,
orig => new List<string>(orig),
clone => clone.Add("asdf"));
Если вам нужна более высокая производительность, это поможет отключить метод,например, создать один метод для каждого типа модификации (Add, Remove, ...), который вы хотите, и жестко закодировать указатели функций cloner
и op
.
NB # 1 Вы несете ответственность за то, чтобы никто не изменил (предположительно) неизменную структуру данных.Мы ничего не можем сделать в универсальной реализации , чтобы предотвратить это, но при специализации на List<T>
вы могли бы защититься от модификации, используя List.AsReadOnly ()
NB # 2 Будьте осторожны со значениями в списке.Приведенный выше подход копирования при записи защищает только их членство в списке, но если вы поместите туда не строки, а некоторые другие изменяемые объекты, вы должны позаботиться о безопасности потоков (например, блокировка).Но это ортогонально этому решению, и, например, блокировка изменяемых значений может быть легко использована без проблем.Вам просто нужно знать об этом.
NB # 3 Если ваша структура данных огромна и вы часто ее изменяете, подход "копировать все при записи" может быть непомерным как вУсловия использования памяти и затраты на копирование процессора.В этом случае вы можете вместо этого использовать неизменные коллекции MS .