Перечислите<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));
});
Благодарю.
6 ответов
Нет! Это совсем не безопасно, потому что processed.Add
не является. Вы можете сделать следующее:
items.AsParallel().Select(item => SomeProcessingFunc(item)).ToList();
Имейте в виду, что Parallel.ForEach
был создан в основном для императивных операций для каждого элемента последовательности. То, что вы делаете, это карта: проецируйте каждое значение последовательности. Что это Select
был создан для. AsParallel
масштабирует его между потоками наиболее эффективным способом.
Этот код работает правильно:
var processed = new List<Guid>();
Parallel.ForEach(items, item =>
{
lock(items.SyncRoot)
processed.Add(SomeProcessingFunc(item));
});
но не имеет смысла с точки зрения многопоточности. lock
Поскольку на каждой итерации выполняется полностью последовательное выполнение, группа потоков будет ожидать один поток.
Использование:
var processed = new ConcurrentBag<Guid>();
Цитирую Джона Скита, прежде чем он доберется сюда:
В рамках Parellel Extensions в.Net 4, в новой
System.Collections.Concurrent
Пространство имен. Они предназначены для обеспечения безопасности при одновременных операциях из нескольких потоков с относительно небольшой блокировкой.
Они включают IProducerConsumerCollection<T>, BlockingCollection<T>, ConcurrentBag<T>, ConcurrentQueue<T>, ConcurrentStack<T>, and ConcurrentDictionary<TKey, TValue>
среди других.
Использование ConcurrentBag типа Something
var bag = new ConcurrentBag<List<Something>>;
var items = GetAllItemsINeed();
Parallel.For(items,i =>
{
bag.Add(i.DoSomethingInEachI());
});
Как альтернатива ответу Андрея:
items.AsParallel().Select(item => SomeProcessingFunc(item)).ToList();
Вы могли бы также написать
items.AsParallel().ForAll(item => SomeProcessingFunc(item));
Это делает запрос, стоящий за ним, еще более эффективным, потому что MSDN не требует слияния. Убедитесь, что SomeProcessingFunc
функция потокобезопасна. И я думаю, но не проверял это, что вам все еще нужна блокировка, если список может быть изменен в другом потоке (добавление или удаление) элементов.
Чтение потокобезопасно, а добавление - нет. Вам нужна настройка блокировки чтения / записи, поскольку добавление может привести к изменению размера внутреннего массива, что может привести к неправильному чтению.
Если вы можете гарантировать, что размер массива не будет изменяться при добавлении, вы можете безопасно добавлять его во время чтения, но не указывайте мне это.
Но на самом деле список - это просто интерфейс к массиву.