[.NET]ConcurrentDictionary 线程安全的集合类
ConcurrentDictionary 是.NET 4.0中新添加的,相信其性能是比自己手动对Dictionary加锁要好得多
其中大部分方法是保证线程安全的:
TryAdd()
TryUpdate()
TryRemove()
AddOrUpdate()
GetOrAdd()
其中有些地方要注意的:
1.作为GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)参数的委托 ,不保证里面代码的线程安全,也不保证委托执行的次数
当要获取的key的value不存在的时候,就会执行委托返回一个新的value值并添加都集合里面去
但是,由于委托并不在ConcurrentDictionary 的锁里面,所以委托里面的代码不是线程安全的,而且不保证委托只被执行一次。
所以要保证委托总是返回确定的值,或者用Lazy<T>代替委托
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Collections.Concurrent;
using System.Threading.Tasks;
namespace ConsoleApplication2
{
class Program
{
static ManualResetEvent Signal = new ManualResetEvent(false);//用于同步线程
static int Count = 0;
static ConcurrentDictionary<int, int> MyDictionary = new ConcurrentDictionary<int, int>();
public static void TaskAction()
{
Signal.WaitOne();
MyDictionary.GetOrAdd(1, key =>
{
Interlocked.Increment(ref Count);//以原子操作的形式递增指定变量的值并存储结果
Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
return 1;
});
}
delegate void myDel();
static void Main(string[] args)
{
myDel del = TaskAction;
List<IAsyncResult> results = new List<IAsyncResult>();
for (int i = 0; i < 4; i++)
{
results.Add(del.BeginInvoke(null, null));
}
Thread.Sleep(3000);//留足够的时间让线程池准备好线程
Signal.Set();
results.ForEach(item => item.AsyncWaitHandle.WaitOne());
Console.WriteLine("执行次数" + Count.ToString());
Console.ReadKey();
}
}
}
上面代码执行的结果可能与原本的结果1是不一致的。
如果采用.NET4.0的Task来试验的话,要同时开几十个线程才偶尔出现错误值,可见Task的优化程度。
2.关于ConcurrentDictionary 的迭代
如果以下面的形式进行迭代,迭代的时候不会阻塞线程,但是会读到脏数据
foreach (var item in MyDictionary)
{
//其他代码
}
如果下面这种形式,迭代的结果将是MyDictionary某一时刻的快照,不包含脏数据
foreach (var item in MyDictionary.ToArray())
{
//其他代码
}
同时,ConcurrentDictionary 的Count, Keys, Values 属性也是某一时刻的快照
3.对ConcurrentDictionary 的某个Value的引用的读写不是线程安全的
下面执行的结果不是可预料的:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Collections.Concurrent;
namespace ConsoleApplication2
{
class Program
{
public class myclass
{
public int value = 0;
}
public static ConcurrentDictionary<int, myclass> MyDictionary = new ConcurrentDictionary<int, myclass>();
static void Main(string[] args)
{
myclass myclass = new Program.myclass();
myclass.value = 0;
MyDictionary.TryAdd(1, myclass);
Thread xxx = new Thread(() =>
{
for (int i = 0; i < 100000000; i++)
{
myclass value;
if (MyDictionary.TryGetValue(1, out value))
{
value.value++;
}
}
});
Thread vvv = new Thread(() =>
{
for (int i = 0; i < 100000000; i++)
{
myclass value;
if (MyDictionary.TryGetValue(1, out value))
{
value.value--;
}
}
});
xxx.Start();
vvv.Start();
xxx.Join();
vvv.Join();
Console.WriteLine(myclass.value);
}
}
}