В тех случаях, когда число операций чтения значительно превышает количество записей или (хотя и часто) записи выполняются не одновременно , копирование при записи подход « может быть уместным.
Реализация, показанная ниже
- беззамочные
- молниеносно быстр для одновременного чтения , даже когда параллельные модификации продолжаются - независимо от того, сколько времени они занимают
- поскольку «моментальные снимки» являются неизменяемыми, атомарность без блокировки возможна, то есть
var snap = _list; snap[snap.Count - 1];
никогда не будет (ну, конечно, за исключением пустого списка) генерировать, и вы также бесплатно получаете потокобезопасное перечисление с семантикой моментальных снимков… как я ЛЮБЛЮ неизменность!
- реализовано в общем , применимо к любой структуре данных и любому типу модификации
- очень простой , т.е. легко тестировать, отлаживать, проверять, читая код
- можно использовать в .Net 3.5
Чтобы копирование при записи работало, вы должны поддерживать неизменную структуру ваших данных , то есть никому не разрешается изменять их после того, как вы сделали их доступными для других потоков. Когда вы хотите изменить, вы
- клонировать структуру
- внести изменения в клон
- атомно поменять местами ссылку на модифицированный клон
Код
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 .