List.Add()线程安全

e36*_*6M3 79 c# asp.net

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

例:

List<object> list = new List<object>();
Parallel.ForEach(transactions, tran =>
{
    list.Add(new object());
});
Run Code Online (Sandbox Code Playgroud)

Tal*_*joe 72

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

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

  • 请注意,没有内置的`ConcurrentList`类型.有并发包,词典,堆栈,队列等,但没有列表. (9认同)

Bro*_*ass 9

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

List<object> list = transactions.AsParallel()
                                .Select( tran => new object())
                                .ToList();
Run Code Online (Sandbox Code Playgroud)

  • 如果使用不需要的话,并发集合可能会破坏你的并行性能 - 你可以做的另一件事就是使用一个固定大小的数组并使用带有索引的`Parallel.Foreach`重载 - 在这种情况下,每个线程都在操作一个不同的数组条目你应该注意安全. (4认同)

tom*_*msv 7

如果您想List.add从多个线程使用并且不关心顺序,那么您可能无论如何都不需要 a 的索引能力List,而应该使用一些可用的并发集合来代替。

如果您忽略此建议而只执行add,则可以使add线程安全,但顺序不可预测,如下所示:

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

...

lock (someListLock)
{
    someList.Add(item);
}
Run Code Online (Sandbox Code Playgroud)

如果您接受这种不可预测的排序,那么您可能如前所述不需要像someList[i].


ala*_*a27 6

我使用ConcurrentBag<T>而不是List<T>这样解决了我的问题:

ConcurrentBag<object> list = new ConcurrentBag<object>();
Parallel.ForEach(transactions, tran =>
{
    list.Add(new object());
});
Run Code Online (Sandbox Code Playgroud)


Lin*_*ron 5

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

有多个潜在的问题......只是不要。如果需要线程安全集合,请使用锁或 System.Collections.Concurrent 集合之一。


Jon*_*nna 5

问这不是不合理的事情。在某些情况下,如果唯一调用的方法与其他方法结合使用会导致线程安全问题,则这些方法是安全的。

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

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

即使EnsureCapacity本身就是线程安全的(而且肯定不是),上述代码显然也不是线程安全的,考虑到同时调用增量运算符可能导致写错的可能性。

在完成工作后,要么使用锁,要么使用ConcurrentList,要么将多个线程写入的位置使用无锁队列,然后直接或通过填充列表从中读取它们(我假设从您的问题来看,在此同时进行多个同时写操作和单线程读取是您的模式,否则我无法看到Add调用唯一方法的条件有什么用)。