Список <T> безопасность потоков

Я использую приведенный ниже код

var processed = new List<Guid>();
Parallel.ForEach(items, item => 
{
    processed.Add(SomeProcessingFunc(item));
});

Является ли указанный выше поток кода безопасным? Есть ли вероятность, что обработанный список будет поврежден? Или я должен использовать блокировку перед добавлением?

var processed = new List<Guid>();
Parallel.ForEach(items, item => 
{
    lock(items.SyncRoot)
        processed.Add(SomeProcessingFunc(item));
});

спасибо.

Ответ 1

Нет! Это небезопасно, потому что processed.Add нет. Вы можете выполнить следующие действия:

items.AsParallel().Select(item => SomeProcessingFunc(item)).ToList();

Имейте в виду, что Parallel.ForEach был создан главным образом для императивных операций для каждого элемента последовательности. Что вы делаете, так это map: project каждое значение последовательности. Для этого был создан Select. AsParallel наиболее эффективно масштабирует его по потокам.

Этот код работает правильно:

var processed = new List<Guid>();
Parallel.ForEach(items, item => 
{
    lock(items.SyncRoot)
        processed.Add(SomeProcessingFunc(item));
});

но не имеет смысла с точки зрения многопоточности. lock при каждой итерационной силе полностью последовательное выполнение, цепочка потоков будет ждать одиночного потока.

Ответ 3

Процитировать Jon Skeet, прежде чем он попадет сюда:

Как часть расширений Parellel в .Net 4, есть несколько новых коллекций в новом System.Collections.ConcurrentПространство имен. Они предназначены для безопасности перед лицом одновременных операций из нескольких потоков, с относительно небольшая блокировка.

К ним относятся, помимо прочего, IProducerConsumerCollection<T>, BlockingCollection<T>, ConcurrentBag<T>, ConcurrentQueue<T>, ConcurrentStack<T>, and ConcurrentDictionary<TKey, TValue>.

Ответ 4

Использование ConcurrentBag типа Something

var bag = new ConcurrentBag<List<Something>>;
var items = GetAllItemsINeed();
Parallel.For(items,i =>                          
   {
      bag.Add(i.DoSomethingInEachI());
   });

Ответ 5

чтение является потокобезопасным, но добавление - нет. Вам нужна установка блокировки чтения/записи, так как добавление может привести к изменению размера внутреннего массива, что испортило бы одновременное чтение.

Если вы можете гарантировать, что массив не будет изменять размер при добавлении, вы можете быть безопасным добавлять во время чтения, но не цитируйте меня на этом.

Но на самом деле список - это просто интерфейс к массиву.

Ответ 6

В качестве альтернативы ответ Андрея:

items.AsParallel().Select(item => SomeProcessingFunc(item)).ToList();

Вы также можете написать

items.AsParallel().ForAll(item => SomeProcessingFunc(item));

Это делает запрос, стоящий за ним еще более эффективным, потому что не требуется слияние, MSDN. Убедитесь, что функция SomeProcessingFunc является потокобезопасной. И я думаю, но не проверял его, что вам по-прежнему нужна блокировка, если список можно изменить в другом потоке (добавлении или удалении) элементов.