List.Add () безопасность потока

Я понимаю, что в общем случае список не является потокобезопасным, однако есть ли что-то плохое в простом добавлении элементов в список, если потоки никогда не выполняют никаких других операций со списком (таких как его обход)?

Пример:

List<object> list = new List<object>();
Parallel.ForEach(transactions, tran =>
{
    list.Add(new object());
});
70 голосов | спросил e36M3 8 AMpFri, 08 Apr 2011 04:58:12 +040058Friday 2011, 04:58:12

9 ответов


0

За кулисами происходит много вещей, включая перераспределение буферов и копирование элементов. Этот код вызовет опасность. Очень просто, нет никаких атомарных операций при добавлении в список, по крайней мере, свойство «Длина» должно быть обновлено, и элемент должен быть помещен в правильное место, и (если есть отдельная переменная), индекс должен быть обновленным. Несколько нитей могут растоптать друг друга. И если требуется рост, то происходит гораздо больше. Если что-то пишет в список, ничто другое не должно читать или писать в него.

В .NET 4.0 у нас есть одновременные коллекции, которые являются поточно-ориентированными и не требуют блокировок.

ответил Talljoe 8 AMpFri, 08 Apr 2011 05:02:21 +040002Friday 2011, 05:02:21
0

Ваш нынешний подход не является потокобезопасным - я бы посоветовал избегать этого вообще - поскольку вы в основном выполняете преобразование данных , PLINQ может быть лучшим подходом (я знаю, что это упрощенный пример, но в конец, вы проецируете каждую транзакцию в другой объект "состояния").

List<object> list = transactions.AsParallel()
                                .Select( tran => new object())
                                .ToList();
ответил BrokenGlass 8 AMpFri, 08 Apr 2011 05:06:23 +040006Friday 2011, 05:06:23
0

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

Есть несколько потенциальных проблем ... Просто не надо. Если вам нужна поточно-безопасная коллекция, используйте блокировку или одну из коллекций System.Collections.Concurrent.

ответил Linkgoron 8 AMpFri, 08 Apr 2011 05:04:47 +040004Friday 2011, 05:04:47
0

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

Однако это явно не тот случай, если учесть код, показанный в отражателе:

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 - единственный вызываемый метод, который может быть полезен).

ответил Jon Hanna 8 AMpFri, 08 Apr 2011 05:12:53 +040012Friday 2011, 05:12:53
0

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

Если вы игнорируете этот совет и делаете только add, вы можете сделать add потокобезопасен, но в непредсказуемом порядке, как это:

private Object someListLock = new Object(); // only once

...

lock (someListLock)
{
    someList.Add(item);
}

Если вы примете это непредсказуемое упорядочение, скорее всего, вам, как упоминалось ранее, не нужна коллекция, которая может выполнять индексацию, как в someList[i] .

ответил tomsv 13 J000000Monday15 2015, 13:51:49
0
  

Что-то не так с простым добавлением элементов в список, если потоки никогда не выполняют никаких других операций в списке?

Краткий ответ: да.

Длинный ответ: запустите программу ниже.

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();
    }
}
ответил Bas Smit 22 22011vEurope/Moscow11bEurope/MoscowTue, 22 Nov 2011 02:00:51 +0400 2011, 02:00:51
0

Как уже говорили другие, вы можете использовать одновременные коллекции из пространства имен System.Collections.Concurrent. Если вы можете использовать один из них, это предпочтительнее.

Но если вы действительно хотите список, который только что синхронизирован, вы можете посмотреть на класс SynchronizedCollection<T> в System.Collections.Generic

Обратите внимание, что вам пришлось включить сборку System.ServiceModel, что также является причиной, по которой мне она не нравится так сильно. Но иногда я им пользуюсь.

ответил maf-soft 25 MarpmMon, 25 Mar 2013 13:34:55 +04002013-03-25T13:34:55+04:0001 2013, 13:34:55
0

Даже добавление элементов в разные потоки небезопасно.

В C # 4.0 есть одновременные коллекции (см. http://jiezhu0815.blogspot.com/2010/08/c-40-feature-1-concurrent-collections.html ).

ответил Richard Schneider 8 AMpFri, 08 Apr 2011 05:07:05 +040007Friday 2011, 05:07:05
0

Я решил свою проблему, используя:

ConcurrentBag<T> 

вместо

IList<T>
  

https: //docs.microsoft.com/dotnet/api/system.collections.concurrent.concurrentbag-1?view=netframework-4.7.2

ответил alansiqueira27 2 +03002018-10-02T17:34:23+03:00312018bEurope/MoscowTue, 02 Oct 2018 17:34:23 +0300 2018, 17:34:23

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132