Я понимаю, что в целом список не является потокобезопасным, однако есть ли что-нибудь плохое в простом добавлении элементов в список, если потоки никогда не выполняют никаких других операций в списке (например, обходят его)?
Пример:
List<object> list = new List<object>();
Parallel.ForEach(transactions, tran =>
{
list.Add(new object());
});
Ответы:
За кулисами происходит множество вещей, включая перераспределение буферов и копирование элементов. Этот код создаст опасность. Очень просто, при добавлении в список нет атомарных операций, по крайней мере, свойство "Длина" должно быть обновлено, а элемент должен быть помещен в нужное место, и (если есть отдельная переменная) индекс должен быть обновленным. Несколько потоков могут давить друг на друга. А если требуется рост, происходит еще много всего. Если что-то записывает в список, ничто другое не должно читать или писать в него.
В .NET 4.0 у нас есть параллельные коллекции, которые легко потокобезопасны и не требуют блокировок.
источник
ConcurrentList
типа нет. Существуют параллельные пакеты, словари, стопки, очереди и т. Д., Но нет списков.Ваш текущий подход не является потокобезопасным - я бы посоветовал вообще этого избежать - поскольку вы в основном выполняете преобразование данных, PLINQ может быть лучшим подходом (я знаю, что это упрощенный пример, но в конечном итоге вы проецируете каждую транзакцию в другое состояние "объект").
List<object> list = transactions.AsParallel() .Select( tran => new object()) .ToList();
источник
Parallel.Foreach
перегрузку, которая принимает индекс - в этом случае каждый поток управляет другой записью массива, и вы должны быть в безопасности.Это не лишний раз спросить. Там являются случаи , когда методы , которые могут вызвать проблемы безопасности потоков в сочетании с другими методами являются безопасными , если они являются единственным методом , называется.
Однако это явно не тот случай, если учесть код, показанный в отражателе:
public void Add(T item) { if (this._size == this._items.Length) { this.EnsureCapacity(this._size + 1); } this._items[this._size++] = item; this._version++; }
Даже если
EnsureCapacity
он сам по себе был потокобезопасным (а это наверняка не так), приведенный выше код явно не будет потокобезопасным, учитывая возможность одновременных вызовов оператора инкремента, вызывающих неправильную запись.Либо заблокируйте, используйте ConcurrentList, либо, возможно, используйте очередь без блокировки в качестве места, куда несколько потоков записывают и читают из него - либо напрямую, либо путем заполнения им списка - после того, как они сделали свою работу (я предполагаю, что несколько одновременных записей с последующим однопоточным чтением - вот ваш образец здесь, судя по вашему вопросу, поскольку в противном случае я не могу понять, как условие, в котором
Add
вызывается единственный вызываемый метод, может быть использовано).источник
Если вы хотите использовать
List.add
из нескольких потоков и не заботитесь об упорядочивании, вам, вероятно, вList
любом случае не нужна возможность индексирования , и вместо этого следует использовать некоторые из доступных параллельных коллекций.Если вы проигнорируете этот совет и сделаете только его
add
, вы сможете сделатьadd
потокобезопасным, но в непредсказуемом порядке, например:private Object someListLock = new Object(); // only once ... lock (someListLock) { someList.Add(item); }
Если вы примете этот непредсказуемый порядок, скорее всего, вам, как упоминалось ранее, не нужна коллекция, которая может выполнять индексацию, как в
someList[i]
.источник
Это может вызвать проблемы, поскольку список построен над массивом и не является потокобезопасным, вы можете получить исключение индекса за пределами границ или некоторые значения, переопределяющие другие значения, в зависимости от того, где находятся потоки. В принципе, не делайте этого.
Есть несколько потенциальных проблем ... Только не надо. Если вам нужна потокобезопасная коллекция, используйте блокировку или одну из коллекций System.Collections.Concurrent.
источник
Я решил свою проблему, используя
ConcurrentBag<T>
вместоList<T>
этого:ConcurrentBag<object> list = new ConcurrentBag<object>(); Parallel.ForEach(transactions, tran => { list.Add(new object()); });
источник
Короткий ответ: да.
Длинный ответ: запустите программу ниже.
using System; using System.Collections.Generic; using System.Linq; using System.Threading; class Program { readonly List<int> l = new List<int>(); const int amount = 1000; int toFinish = amount; readonly AutoResetEvent are = new AutoResetEvent(false); static void Main() { new Program().Run(); } void Run() { for (int i = 0; i < amount; i++) new Thread(AddTol).Start(i); are.WaitOne(); if (l.Count != amount || l.Distinct().Count() != amount || l.Min() < 0 || l.Max() >= amount) throw new Exception("omg corrupted data"); Console.WriteLine("All good"); Console.ReadKey(); } void AddTol(object o) { // uncomment to fix // lock (l) l.Add((int)o); int i = Interlocked.Decrement(ref toFinish); if (i == 0) are.Set(); } }
источник
Как уже говорили другие, вы можете использовать параллельные коллекции из
System.Collections.Concurrent
пространства имен. Если вы можете использовать один из них, это предпочтительнее.Но если вам действительно нужен список, который просто синхронизируется, вы можете посмотреть
SynchronizedCollection<T>
-Class вSystem.Collections.Generic
.Обратите внимание, что вам пришлось включить сборку System.ServiceModel, что также является причиной, по которой мне она так не нравится. Но иногда пользуюсь.
источник
Даже добавление элементов в разные потоки не является потокобезопасным.
В C # 4.0 есть параллельные коллекции (см. Http://jiezhu0815.blogspot.com/2010/08/c-40-feature-1-concurrent-collections.html ).
источник