Список <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
при каждой итерационной силе полностью последовательное выполнение, цепочка потоков будет ждать одиночного потока.
Ответ 2
Использование:
var processed = new ConcurrentBag<Guid>();
Смотрите параллельный цикл foreach - нечетное поведение.
Ответ 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
является потокобезопасной.
И я думаю, но не проверял его, что вам по-прежнему нужна блокировка, если список можно изменить в другом потоке (добавлении или удалении) элементов.