C# 多线程同时往一个Dictionary Add数据时产生数组越界问题的原因分析
问题描述:
观察以下代码,多线程同时向一个Dictionary中Add数据,运行后发现报数组越界的错误
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace CSharpTest2
{
class Program
{
public static void Main(string[] args)
{
Dictionary<int, int> map = new Dictionary<int, int>();
List<Task> tasks = new List<Task>();
for(int i = 0; i < 100; i++)
{
int k = i;
tasks.Add(Task.Run(() =>
{
map.Add(k, k);
}));
}
Task.WaitAll(tasks.ToArray());
Console.WriteLine(tasks.Count);
}
}
}
错误产生原因:
要知道错误原因,就需要先了解Dictionary的Add方法是如何工作的。
简单的说:Dictionary是动态增长的,Dictionary的Add方法在工作时,会先检查Dictionary容量是否足够,如果容量足够,就放里放数据,也就是说,先检查,后添加。
但是,多线程工作时,可能产生这么一种情况,就是当Dictionary剩余容量只有一个时,多个线程同时检查,都会发现Dictionary容量足够,然后都往里放数据,结果造成数组越界。
我们再做一个实验
不是说容量不够引起了数组越界问题吗?那我创建Dictionary时指定足够的容量应该就能避免这样的问题吧
因此我们修改代码
//Dictionary<int, int> map = new Dictionary<int, int>();
Dictionary<int, int> map = new Dictionary<int, int>(1000);
再次运行时发现确实没有出现问题了,这证明了我们的观点
为了进一步理解错误产生原因,我们查看Dictionary源码
发现Add方法其实时这样的
public void Add(TKey key, TValue value) {
Insert(key, value, true);
}
而Insert方法中存在以下代码
......
else {
if (count == entries.Length)
{
Resize();
targetBucket = hashCode % buckets.Length;
}
index = count;
count++;
}
entries[index].hashCode = hashCode;
entries[index].next = buckets[targetBucket];
entries[index].key = key;
entries[index].value = value;
......
其中
if (count == entries.Length)
{
Resize();
targetBucket = hashCode % buckets.Length;
}
就是在判断Dictionary容量是否足够,如果容量不够则扩容
而
entries[index].hashCode = hashCode;
entries[index].next = buckets[targetBucket];
entries[index].key = key;
entries[index].value = value;
就是将数据存储进Dictionary
显然,这样的先检查后添加的逻辑,在多线程同时往一个Dictionary Add数据时,完全可能造成“容量足够”的误判