我知道一般来说,List不是线程安全的,但是如果线程从不对列表执行任何其他操作(例如遍历),那么简单地将项目添加到列表中有什么错吗?范例:
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());
});
字符串
xzabzqsa1#
在后台会发生很多事情,包括重新分配缓冲区和复制元素。这些代码会导致危险。很简单,添加到列表时没有原子操作,至少Length属性需要更新,并且需要将item放入正确的位置,和(如果有单独的变量)索引需要更新。多个线程可能会互相践踏。如果需要增长,那么就会有更多的事情发生。如果有东西正在写入列表,没有其他东西应该对它进行阅读或写。在.NET 4.0中,我们有并发集合,它是线程安全的,不需要锁。
Length
xurqigkl2#
你目前的方法不是线程安全的-我建议完全避免这一点-因为你基本上做了一个 * 数据转换 * PLINQ可能是一个更好的方法(我知道这是一个简化的例子,但最终你将每个事务投射到另一个“状态”对象)。
List<object> list = transactions.AsParallel() .Select( tran => new object()) .ToList();
List<object> list = transactions.AsParallel()
.Select( tran => new object())
.ToList();
t98cgbkg3#
我使用ConcurrentBag<T>而不是List<T>解决了我的问题:
ConcurrentBag<T>
List<T>
ConcurrentBag<object> list = new ConcurrentBag<object>();Parallel.ForEach(transactions, tran =>{ list.Add(new object());});
ConcurrentBag<object> list = new ConcurrentBag<object>();
0tdrvxhp4#
如果你想从多个线程中使用List.add,并且不关心顺序,那么你可能不需要List的索引功能,而应该使用一些可用的并发集合。如果你忽略这个建议,只做add,你可以让add线程安全,但以不可预测的顺序如下:
List.add
List
add
private Object someListLock = new Object(); // only once...lock (someListLock){ someList.Add(item);}
private Object someListLock = new Object(); // only once
...
lock (someListLock)
someList.Add(item);
}
字符串如果您接受这种不可预测的顺序,那么很可能您不需要像someList[i]那样可以进行索引的集合。
someList[i]
voase2hg5#
这不是一个不合理的问题。在某些情况下,如果方法是唯一被调用的方法,那么与其他方法结合使用可能会导致线程安全问题的方法是安全的。然而,当你考虑reflector中显示的代码时,这显然不是一个案例:
public void Add(T item){ if (this._size == this._items.Length) { this.EnsureCapacity(this._size + 1); } this._items[this._size++] = item; this._version++;}
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是唯一调用的方法的条件有什么用处)。
EnsureCapacity
Add
zwghvu4y6#
这会导致问题,因为List是在数组上构建的,不是线程安全的,你可能会得到索引越界异常或某些值覆盖其他值,这取决于线程的位置。有很多潜在的问题......不要。如果你需要一个线程安全的集合,要么使用锁,要么使用System.Collections.Concurrent collections。
cidc1ykv7#
如果线程从不在列表上执行任何其他操作,那么简单地将项目添加到列表中有什么错吗?简短的回答:是的。详细回答:运行下面的程序。
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(); }}
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();
5us2dqdw8#
正如其他人已经说过的,你可以使用来自System.Collections.Concurrent命名空间的并发集合。如果你可以使用其中之一,这是首选。但是如果你真的想要一个只是同步的列表,你可以看看System.Collections.Generic中的SynchronizedCollection<T>-Class。请注意,您必须包含System.ServiceModel程序集,这也是我不太喜欢它的原因,但有时我会使用它。
System.Collections.Concurrent
System.Collections.Generic
SynchronizedCollection<T>
t3irkdon9#
即使在不同的线程上添加元素也不是线程安全的。在C# 4.0中有并发集合(参见http://jiezhu0815.blogspot.com/2010/08/c-40-feature-1-concurrent-collections.html)。
9条答案
按热度按时间xzabzqsa1#
在后台会发生很多事情,包括重新分配缓冲区和复制元素。这些代码会导致危险。很简单,添加到列表时没有原子操作,至少
Length
属性需要更新,并且需要将item放入正确的位置,和(如果有单独的变量)索引需要更新。多个线程可能会互相践踏。如果需要增长,那么就会有更多的事情发生。如果有东西正在写入列表,没有其他东西应该对它进行阅读或写。在.NET 4.0中,我们有并发集合,它是线程安全的,不需要锁。
xurqigkl2#
你目前的方法不是线程安全的-我建议完全避免这一点-因为你基本上做了一个 * 数据转换 * PLINQ可能是一个更好的方法(我知道这是一个简化的例子,但最终你将每个事务投射到另一个“状态”对象)。
字符串
t98cgbkg3#
我使用
ConcurrentBag<T>
而不是List<T>
解决了我的问题:字符串
0tdrvxhp4#
如果你想从多个线程中使用
List.add
,并且不关心顺序,那么你可能不需要List
的索引功能,而应该使用一些可用的并发集合。如果你忽略这个建议,只做
add
,你可以让add
线程安全,但以不可预测的顺序如下:字符串
如果您接受这种不可预测的顺序,那么很可能您不需要像
someList[i]
那样可以进行索引的集合。voase2hg5#
这不是一个不合理的问题。在某些情况下,如果方法是唯一被调用的方法,那么与其他方法结合使用可能会导致线程安全问题的方法是安全的。
然而,当你考虑reflector中显示的代码时,这显然不是一个案例:
字符串
即使
EnsureCapacity
本身是线程安全的(它肯定不是),考虑到同时调用增量操作符导致错误写入的可能性,上面的代码显然不是线程安全的。可以使用ConcurrentList加锁,也可以使用无锁队列作为多个线程写入和读取的位置-直接或通过填充一个列表-在它们完成工作之后(从您的问题来看,我假设多个并发写入后跟单线程阅读是您的模式,否则我看不出
Add
是唯一调用的方法的条件有什么用处)。zwghvu4y6#
这会导致问题,因为List是在数组上构建的,不是线程安全的,你可能会得到索引越界异常或某些值覆盖其他值,这取决于线程的位置。
有很多潜在的问题......不要。如果你需要一个线程安全的集合,要么使用锁,要么使用System.Collections.Concurrent collections。
cidc1ykv7#
如果线程从不在列表上执行任何其他操作,那么简单地将项目添加到列表中有什么错吗?
简短的回答:是的。
详细回答:运行下面的程序。
字符串
5us2dqdw8#
正如其他人已经说过的,你可以使用来自
System.Collections.Concurrent
命名空间的并发集合。如果你可以使用其中之一,这是首选。但是如果你真的想要一个只是同步的列表,你可以看看
System.Collections.Generic
中的SynchronizedCollection<T>
-Class。请注意,您必须包含System.ServiceModel程序集,这也是我不太喜欢它的原因,但有时我会使用它。
t3irkdon9#
即使在不同的线程上添加元素也不是线程安全的。
在C# 4.0中有并发集合(参见http://jiezhu0815.blogspot.com/2010/08/c-40-feature-1-concurrent-collections.html)。