List.Add()线程安全

时间:2011-04-08 00:58:12

标签: c# asp.net

据我所知,一般来说List不是线程安全的,但是如果线程从不在列表上执行任何其他操作(例如遍历它),那么简单地将项添加到列表中是否有任何问题?

示例:

List<object> list = new List<object>();
Parallel.ForEach(transactions, tran =>
{
    list.Add(new object());
});

9 个答案:

答案 0 :(得分:70)

幕后有很多事情发生,包括重新分配缓冲区和复制元素。该代码将导致危险。很简单,添加到列表时没有原子操作,至少“长度”属性需要更新,项目需要放在正确的位置,并且(如果有单独的变量)索引需要要被更新。多个线程可以互相践踏。如果需要增长,那么还有很多事情要发生。如果某些内容正在写入列表,那么其他任何内容都不应该是读取或写入它。

在.NET 4.0中,我们有并发集合,它们很容易线程安全,不需要锁定。

答案 1 :(得分:9)

你当前的方法不是线程安全的 - 我建议完全避免这种情况 - 因为你基本上做了数据转换 PLINQ可能是一个更好的方法(我知道这是一个简化的例子,但在你将每个交易投射到另一个“状态”对象。)

List<object> list = transactions.AsParallel()
                                .Select( tran => new object())
                                .ToList();

答案 2 :(得分:5)

这不是一个不合理的问题。 的情况,如果它们是唯一被调用的方法,那么与其他方法结合可能导致线程安全问题的方法是安全的。

然而,当您考虑反射器中显示的代码时,显然不是这种情况:

public void Add(T item)
{
    if (this._size == this._items.Length)
    {
        this.EnsureCapacity(this._size + 1);
    }
    this._items[this._size++] = item;
    this._version++;
}

即使EnsureCapacity本身是线程安全的(并且肯定不是),上面的代码显然不会是线程安全的,考虑到同时调用增量运算符导致误写的可能性。 / p>

要么锁定,要么使用ConcurrentList,要么使用无锁队列作为多个线程写入的位置,并在它们完成工作后直接或通过填充列表来读取它(我' m假设多个同时写入后跟单线程读取是你的模式,从你的问题判断,否则我无法看到Add是唯一被调用的方法可能有用的条件。

答案 3 :(得分:4)

这会导致问题,因为List是在数组上构建的并且不是线程安全的,您可能会获得索引超出范围的异常或某些值覆盖其他值,具体取决于线程的位置。基本上,不要这样做。

存在多种潜在问题......只是不要。如果您需要线程安全集合,请使用锁或其中一个System.Collections.Concurrent集合。

答案 4 :(得分:3)

如果你想从多个线程使用List.add并且不关心排序,那么你可能不需要List的索引能力,并且应该使用一些可用的并发而是收藏品。

如果你忽略了这个建议并且只做了add,那么你可以使add线程安全但是不可预测的顺序如下:

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

...

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

如果您接受这种不可预测的排序,那么您前面提到的可能不需要像someList[i]那样可以进行索引的集合。

答案 5 :(得分:1)

  

如果线程从不在列表上执行任何其他操作,那么简单地将项目添加到列表中是否有任何问题?

简答:是的。

答案很长:运行以下程序。

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();
    }
}

答案 6 :(得分:1)

正如其他人已经说过的那样,您可以使用System.Collections.Concurrent命名空间中的并发集合。如果您可以使用其中之一,则首选。

但如果你真的想要一个刚刚同步的列表,你可以查看SynchronizedCollection<T>中的System.Collections.Generic - 类。

请注意,您必须包含System.ServiceModel程序集,这也是我不喜欢它的原因。但有时我会用它。

答案 7 :(得分:0)

即使在不同的线程上添加元素也不是线程安全的。

在C#4.0中,有并发集合(参见http://jiezhu0815.blogspot.com/2010/08/c-40-feature-1-concurrent-collections.html)。

答案 8 :(得分:0)

我使用以下方法解决了我的问题:

ConcurrentBag<T> 

代替

IList<T>
  

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