несколько потоков, добавляющих элементы в один список. почему в списке всегда меньше элементов, чем ожидалось? - PullRequest
11 голосов
/ 26 марта 2012

Следующий код объясняет мой вопрос.Я знаю, что список не является потокобезопасным.Но какова основная «настоящая» причина этого?

    class Program
{
    static void Main(string[] args)
    {
        List<string> strCol = new List<string>();

        for (int i = 0; i < 10; i++)
        {
            int id = i;
            Task.Factory.StartNew(() =>
            {
                AddElements(strCol);
            }).ContinueWith((t) => { WriteCount(strCol, id.ToString()); });
        }

        Console.ReadLine();
    }

    private static void WriteCount(List<string> strCol, string id)
    {
        Console.WriteLine(string.Format("Task {0} is done. Count: {1}. Thread ID: {2}", id, strCol.Count, Thread.CurrentThread.ManagedThreadId));
    }

    private static void AddElements(List<string> strCol)
    {
        for (int i = 0; i < 20000; i++)
        {
            strCol.Add(i.ToString());
        }
    }
}

Ответы [ 2 ]

19 голосов
/ 27 марта 2012

Я пропущу очевидный ответ "Список не является потокобезопасным" - это вы уже знаете.

Элементы списка хранятся во внутреннем массиве. Существует как минимум два этапа (с логической точки зрения) при добавлении элемента в список. Сначала List получает индекс, указывающий, куда поместить новый элемент. Он помещает новый элемент в массив, используя этот индекс. Затем он увеличивает индекс, и это второй этап. Если второй (или третий, четвертый, ...) поток добавляет новый элемент в то же время, возможно, что два (3, 4, ...) новых элемента будут помещены в одно и то же место массива перед индексом увеличивается на первый поток. Элементы перезаписываются и теряются.

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

Надеюсь, это немного объясняет.

13 голосов
/ 26 марта 2012

Это потому, что List<T> не является потокобезопасным.

Для этого следует использовать потокобезопасную коллекцию, например одну из коллекций в System.Collections.Concurrent . В противном случае вам нужно будет синхронизировать весь доступ к List<T> (то есть: помещать каждый вызов Add в блокировку), что лишит цели вызова этого, используя несколько потоков полностью, поскольку вы не делаете никаких других работать в этой ситуации.

...