多个线程将元素添加到一个列表。为什么列表中的项目总是少于预期?

时间:2012-03-26 18:08:18

标签: c# multithreading concurrency parallel-processing task-parallel-library

以下代码解释了我的问题。 我知道列表不是线程安全的。但是这个潜在的“真正”原因是什么?

    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 个答案:

答案 0 :(得分:19)

我将跳过明显的答案“列表不是线程安全的” - 你已经知道了。

列表项目保存在内部数组中。将项目添加到List时,至少有两个阶段(从逻辑的角度来看)。首先,List获取一个索引,指示新项目的放置位置。它使用此索引将新项目放入数组中。然后它递增索引,这是第二阶段。如果第二个(或第三个,第四个......)线程同时添加新项目,则可能会有两个(3,4,...)新项目放入索引之前的同一个数组位置由第一个线程递增。项目被覆盖并丢失。

添加新项目和递增索引的内部操作必须始终一次完成,以使列表成为线程安全的。那就是所谓的关键部分。这可以通过锁来实现。

希望这能解释一下。

答案 1 :(得分:13)

这是因为List<T>不是线程安全的。

您应该为此使用线程安全集合,例如System.Collections.Concurrent中的一个集合。否则,您将需要同步对List<T>的所有访问(即:将每个Add调用放入锁中),这将完全无法使用多个线程调用此目的,因为您是在这种情况下没有做任何其他工作。