C# List 并发丢数据问题原因及解决方案
问题描述
在多线程环境下,使用C#的List
时,会存在添加元素丢失、重复、越界等问题,导致程序出现异常或不可预料的结果。这是由于List
本身并不是线程安全的集合类,多个线程同时对其进行写操作时,会导致竞争条件,从而出现数据异常。
原因分析
List
是一个基于数组的集合类型,当多个线程同时对其进行写操作时,可能会导致以下问题:
- 数据丢失:当一个线程正在向
List
中添加元素时,另一个线程同时也在添加元素,会出现覆盖或丢失数据的情况。 - 重复数据:当多个线程同时以相同的方式向
List
中添加元素时,可能会导致出现重复数据。 - 越界异常:当一个线程正在添加元素时,另一个线程在同一时间删除或插入元素,就会破坏
List
的数据结构,导致越界异常。
解决方案
为了避免以上问题,我们可以采用以下三种解决方案中的一种:
方案一:加锁
使用lock
语句锁定List
,使得同一时间只能有一个线程对其进行写操作。代码示例如下:
private static readonly object _lockObj = new object();
private static List<int> _list = new List<int>();
public void AddItem(int item)
{
lock (_lockObj)
{
_list.Add(item);
}
}
由于每个线程都需要获得锁对象才能进行写操作,因此会降低部分性能。
方案二:使用ConcurrentBag
System.Collections.Concurrent
命名空间提供了一些线程安全的集合类,其中ConcurrentBag
可以用来代替List
。它是一种 “无序” 的集合,内部采用了无锁算法,使得多个线程可以同时进行写操作。示例代码如下:
private static ConcurrentBag<int> _bag = new ConcurrentBag<int>();
public void AddItem(int item)
{
_bag.Add(item);
}
使用ConcurrentBag
不需要加锁,因为其内部已经采用了无锁算法,性能较高。
方案三:使用线程安全的List
.NET 4.5中提供了一个名为System.Collections.Generic.ThreadSafe
的线程安全集合类库,其中有一个ThreadSafeList<T>
类可以替代List
private static ThreadSafeList<int> _threadSafeList = new ThreadSafeList<int>();
public void AddItem(int item)
{
_threadSafeList.Add(item);
}
ThreadSafeList
内部采用了类似于方案一的加锁机制,但是封装了这个操作,使用起来便捷性更高。
示例说明
我们使用以下代码来模拟这个问题:
private static List<int> _list = new List<int>();
public static void Main(string[] args)
{
Task.Factory.StartNew(AddItem);
Task.Factory.StartNew(AddItem);
Console.ReadLine();
}
public static void AddItem()
{
var rand = new Random();
for (int i = 0; i < 100; i++)
{
var item = rand.Next(100);
_list.Add(item);
}
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} completed.");
Console.WriteLine(string.Join(',', _list));
}
这里我们模拟了两个线程分别向List
中添加100个随机数字,运行结果如下:
...
1,3,8,60,43,63,10,52,89,6,46,11,40,74,16,1,3,8,60,43,63,10,52,89,6,46,11,40,74,16,1,3,
8,60,43,63,10,52,89,6,46,11,40,74,16,1,3,8,60,43,63,10,52,89,6,46,11,40,74,16,1,3,8,60,
43,63,10,52,89,6,46,11,40,74,16,Thread 4 completed.
1,3,8,60,43,63,10,52,89,6,46,11,40,74,16,1,3,8,60,43,63,10,52,89,6,46,11,40,74,16,1,3,
8,60,43,63,10,52,89,6,46,11,40,74,16,1,3,8,60,43,63,10,52,89,6,46,11,40,74,16,1,3,8,60,
43,63,10,52,89,6,46,11,40,74,16,Thread 3 completed.
...
从输出结果可以发现,最后List
中只包含了一个线程的数据,另一个线程的数据被覆盖了。这是由于多个线程同时对List
进行写操作,导致数据不一致。如若使用方案一或方案三,则可以改为使用lock
或ThreadSafeList
,就可以避免这个问题。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C# List 并发丢数据问题原因及解决方案 - Python技术站