Список ‹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));
});

благодаря.


person stackoverflowuser    schedule 16.02.2011    source источник
comment
Вы смотрели MSDN? Здесь: msdn.microsoft.com/ en-us / library /   -  person R. Martinho Fernandes    schedule 16.02.2011
comment
См. stackoverflow.com/questions/4779165/ .   -  person mellamokb    schedule 16.02.2011
comment
@ Мартиньо: Да. Я читал, что List ‹T› не является потокобезопасным. Но я не могу понять, что даже если несколько потоков добавляются в список, как это может повредить список.   -  person stackoverflowuser    schedule 16.02.2011
comment
@stackoverflowuser: пример: список отслеживает, сколько элементов в нем есть. Когда вы добавляете один, он помещает его в следующую позицию и увеличивает счетчик. Что ж, тут есть состояние гонки: два потока могут добавлять элемент, а счетчик может увеличиваться только на один (и один элемент теряется в процессе).   -  person R. Martinho Fernandes    schedule 16.02.2011
comment
@mellmokb: спасибо за ссылку. Это было полезно.   -  person stackoverflowuser    schedule 16.02.2011
comment
@Martinho: отличный ответ. это прояснило. благодаря.   -  person stackoverflowuser    schedule 16.02.2011
comment
Я рекомендую вместо использования var использовать известный тип.   -  person mozillanerd    schedule 16.02.2011


Ответы (6)


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

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

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

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

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

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

person Andrey    schedule 16.02.2011
comment
чтобы понять, почему: см. msdn.microsoft.com/en-us/library/ 6sh2ey19.aspx ближе к концу у него есть тема безопасности потоков - person rene; 16.02.2011
comment
@rene: прикрепите #c9721fa0-1cd9-4a21-818c-98d164c9fc14 к концу этого адреса, и он укажет прямо на соответствующий раздел;) - person R. Martinho Fernandes; 16.02.2011
comment
Спасибо за ответ. Было бы лучше использовать ConcurrentBag ‹T›, как упоминалось здесь stackoverflow.com/questions/4779165/ - person stackoverflowuser; 16.02.2011
comment
Можете ли вы, пожалуйста. предоставьте свои данные о том, будет ли AsParallel лучше, чем использование ConcurrentBag ‹T›? - person stackoverflowuser; 16.02.2011
comment
@stackoverflowuser да, когда вам действительно нужен параллельный доступ к структуре данных, вы должны использовать ConcurrentBag<T>. но на самом деле с такой высокоуровневой библиотекой, как PLINQ, она нужна редко. - person Andrey; 16.02.2011

Использовать:

var processed = new ConcurrentBag<Guid>();

См. параллельный цикл foreach - странное поведение.

person mellamokb    schedule 16.02.2011

Из книги Джона Скита Подробнее о C #:

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

К ним относятся:

  • IProducerConsumerCollection<T>
  • BlockingCollection<T>
  • ConcurrentBag<T>
  • ConcurrentQueue<T>
  • ConcurrentStack<T>
  • ConcurrentDictionary<TKey, TValue>
  • и другие
person DOK    schedule 16.02.2011
comment
Согласованный. См. Мой ответ, в котором используется тип из пространства имен System.Collections.Concurrent. - person mellamokb; 16.02.2011

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

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

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

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

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

person Christophe Lambrechts    schedule 07.08.2012

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

var bag = new ConcurrentBag<List<Something>>;
var items = GetAllItemsINeed();
Parallel.For(items,i =>                          
   {
      bag.Add(i.DoSomethingInEachI());
   });
person JWP    schedule 24.01.2016
comment
должно быть var bag = new ConcurrentBag<Something>(); - person Artemious; 14.06.2021

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

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

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

person Bengie    schedule 16.02.2011