我知道一般来说,List 不是线程安全的,但如果线程从未执行其他操作(比如遍历),那么仅向 List 中添加项目会有问题吗?
例子:
List<object> list = new List<object>();
Parallel.ForEach(transactions, tran =>
{
list.Add(new object());
});
我知道一般来说,List 不是线程安全的,但如果线程从未执行其他操作(比如遍历),那么仅向 List 中添加项目会有问题吗?
例子:
List<object> list = new List<object>();
Parallel.ForEach(transactions, tran =>
{
list.Add(new object());
});
Length
属性,并将项目放在正确的位置,(如果有单独的变量)需要更新索引。多个线程可能会相互干扰。如果需要扩展,则会有更多的操作。如果有东西正在写入列表,其他任何东西都不应该读取或写入它。你目前的方法不是线程安全的 - 我建议完全避免使用这种方法 - 因为你基本上进行了数据转换,PLINQ可能是更好的方法(我知道这只是一个简化的例子,但最终你会将每个事务投影到另一个“状态”对象中)。
List<object> list = transactions.AsParallel()
.Select( tran => new object())
.ToList();
Parallel.ForEach
重载,该重载接受索引 - 在这种情况下,每个线程都在操作不同的数组条目,因此您可以放心使用。 - BrokenGlass我使用 ConcurrentBag<T>
替代了 List<T>
,这样解决了我的问题:
ConcurrentBag<object> list = new ConcurrentBag<object>();
Parallel.ForEach(transactions, tran =>
{
list.Add(new object());
});
List.add
并且不关心排序,那么您可能根本不需要List
的索引功能,而应该使用一些可用的并发集合。add
,您可以使add
线程安全,但顺序是不可预测的,如下所示:private Object someListLock = new Object(); // only once
...
lock (someListLock)
{
someList.Add(item);
}
someList[i]
。这不是一个不合理的要求。有些情况下,某些方法与其他方法组合使用可能会导致线程安全问题,但如果它们是唯一被调用的方法,则是安全的。
然而,当您考虑在反射器中显示的代码时,这显然不是这种情况:
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
方法的条件有任何用处)。如果线程从未对列表执行其他操作,仅将项添加到列表中是否有任何问题?
简短回答:是的。
详细回答:运行以下程序。
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();
}
}
正如其他人所说,您可以使用System.Collections.Concurrent
命名空间中的并发集合。如果您可以使用其中之一,则应首选。
但是,如果您真的想要一个仅同步的列表,您可以查看System.Collections.Generic
中的SynchronizedCollection<T>
类。
请注意,您必须包含System.ServiceModel程序集,这也是我不太喜欢它的原因。但有时我会使用它。
即使在不同的线程上添加元素也不是线程安全的。
C# 4.0中有并发集合类(参见 http://jiezhu0815.blogspot.com/2010/08/c-40-feature-1-concurrent-collections.html)。