我知道一般来说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中,我们有并发集合,它们易于线程安全且不需要锁定.
你当前的方法不是线程安全的 - 我建议完全避免这种情况 - 因为你基本上做了数据转换 PLINQ可能是一个更好的方法(我知道这是一个简化的例子,但最终你将每个事务投射到另一个"状态" "对象".
List<object> list = transactions.AsParallel()
.Select( tran => new object())
.ToList();
Run Code Online (Sandbox Code Playgroud)
如果您想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].
我使用ConcurrentBag<T>而不是List<T>这样解决了我的问题:
ConcurrentBag<object> list = new ConcurrentBag<object>();
Parallel.ForEach(transactions, tran =>
{
list.Add(new object());
});
Run Code Online (Sandbox Code Playgroud)
这会导致问题,因为 List 是在数组上构建的,并且不是线程安全的,您可能会得到索引越界异常或某些值覆盖其他值,具体取决于线程所在的位置。基本上,不要这样做。
有多个潜在的问题......只是不要。如果需要线程安全集合,请使用锁或 System.Collections.Concurrent 集合之一。
问这不是不合理的事情。在某些情况下,如果唯一调用的方法与其他方法结合使用会导致线程安全问题,则这些方法是安全的。
但是,当考虑反射器中显示的代码时,显然不是这种情况:
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调用唯一方法的条件有什么用)。