我了解通常来说,列表不是线程安全的,但是如果线程从不对列表执行任何其他操作(例如遍历列表),则简单地将项目添加到列表中有什么错误吗?
例:
List<object> list = new List<object>();
Parallel.ForEach(transactions, tran =>
{
list.Add(new object());
});
我了解通常来说,列表不是线程安全的,但是如果线程从不对列表执行任何其他操作(例如遍历列表),则简单地将项目添加到列表中有什么错误吗?
例:
List<object> list = new List<object>();
Parallel.ForEach(transactions, tran =>
{
list.Add(new object());
});
Answers:
在幕后发生了很多事情,包括重新分配缓冲区和复制元素。该代码将导致危险。非常简单,添加到列表时没有原子操作,至少需要更新“长度”属性,并且需要将项放在正确的位置,并且(如果有单独的变量)索引也需要要被更新。多个线程可以相互踩踏。如果需要增长,那么还有更多的事情要做。如果某项内容正在写入列表,则其他任何内容都不应读取或写入。
在.NET 4.0中,我们有并发集合,它们很容易实现线程安全并且不需要锁。
ConcurrentList类型。有并发的包,字典,堆栈,队列等,但没有列表。
您当前的方法不是线程安全的-我建议完全避免这种情况-因为您基本上进行了数据转换PLINQ可能是一种更好的方法(我知道这是一个简化的示例,但最终您将每个事务投影到另一个“状态”中”对象)。
List<object> list = transactions.AsParallel()
.Select( tran => new object())
.ToList();
Parallel.Foreach引入索引的重载-在这种情况下,每个线程都在操纵不同的数组项,因此您应该是安全的。
要求这不是不合理的事情。在某些情况下,如果它们是唯一调用的方法,则与其他方法结合使用会导致线程安全问题的方法是安全的。
但是,当考虑反射器中显示的代码时,显然不是这种情况:
public void Add(T item)
{
if (this._size == this._items.Length)
{
this.EnsureCapacity(this._size + 1);
}
this._items[this._size++] = item;
this._version++;
}
即使EnsureCapacity它本身是线程安全的(并且肯定不是),考虑到同时调用增量运算符导致错误写入的可能性,上述代码显然也不是线程安全的。
在完成工作后,要么使用锁,要么使用ConcurrentList,要么使用无锁队列作为多个线程写入的位置,然后直接或通过填充列表从中读取(我假设从您的问题来看,在此同时进行多个同时写操作和单线程读取是您的模式,否则我无法看到Add调用唯一方法的条件有什么用。
我使用ConcurrentBag<T>而不是List<T>这样解决了我的问题:
ConcurrentBag<object> list = new ConcurrentBag<object>();
Parallel.ForEach(transactions, tran =>
{
list.Add(new object());
});
如果线程从不对列表执行任何其他操作,仅将项目添加到列表中有什么问题吗?
简短的回答:是的。
长答案:运行下面的程序。
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();
}
}
即使在不同线程上添加元素也不是线程安全的。
在C#4.0中,存在并发集合(请参见http://jiezhu0815.blogspot.com/2010/08/c-40-feature-1-concurrent-collections.html)。