Microsoft.Practices.Unity 的一个线程安全Bug浅析
从接触Enterprise Library开始,到现在越来越感觉的Elib的强大。虽然单独看Elib里面的Block不一定是优秀的,但是作为一个整体其优势不言而喻。更重要的是Elib是MS的团队在维护,不用担心MS会把它吃掉。
这段时间一直在Elib上进行开发,Unity也是用的最多的一个Block了。由于都是在单机开发BS系统,因此很少应用到多线程,多线程的问题也没有怎么暴露出来。以前也潜意识的认为MS会把线程安全处理好,但是今天发现其实不然。测试代码如下:
1 class Program
3 static UnityContainer container;
4 static bool stop;
5 static void Main(string[] args)
6 {
7 container = new UnityContainer();
8
9 var t1 = new Thread(new ThreadStart(Read));
10 var t2 = new Thread(new ThreadStart(Write));
11
12 t1.Start();
13 t2.Start();
14
15 Console.ReadLine();
16 stop = true;
17 }
18
19 static void Read()
20 {
21 while (true)
22 {
23 if (container.IsRegistered<int>("a"))
24 Console.WriteLine("OK");
25 Thread.Sleep(10);
26 if (stop)
27 break;
28 }
29 }
30
31 static void Write()
32 {
33 int index = 0;
34 while (true)
35 {
36 container.RegisterType<Program>(index.ToString());
37 Console.WriteLine("Regist " + index);
38 Thread.Sleep(10);
39 index++;
40 if (stop)
41 break;
42 }
43 }
44 }
运行这个简单的sample,不久就会出现:Collection was modified; enumeration operation may not execute.异常,就是说在循环一个集合时,这个集合被另一线程修改了。这个错误在多线程编程中算比较经典的异常了。查看Unity的Regist方法的代码发现其实现其实非常简单。
public static bool IsRegistered(this IUnityContainer container, Type typeToCheck, string nameToCheck)
Guard.ArgumentNotNull(container, "container");
Guard.ArgumentNotNull(typeToCheck, "typeToCheck");
var registration = from r in container.Registrations
where r.RegisteredType == typeToCheck && r.Name == nameToCheck
select r;
return registration.FirstOrDefault() != null;
}
通过遍历一个Resitrations查找符合记录的条件。Registrations通过
{
if(parent != null)
{
parent.FillTypeRegistrationDictionary(typeRegistrations);
}
foreach(Type t in registeredNames.RegisteredTypes)
{
if(!typeRegistrations.ContainsKey(t))
{
typeRegistrations[t] = new List<string>();
}
typeRegistrations[t] =
(typeRegistrations[t].Concat(registeredNames.GetKeys(t))).Distinct().ToList();
}
}
方法得到。到这里就可看到整个实现的核心所在:NamedTypesRegistry类型的registeredNames。这个类负责类型的注册,所有的注册信息都保存在这个类中。重点就在 public void RegisterType(Type t, string name)
if(!registeredKeys.ContainsKey(t))
{
registeredKeys[t] = new List<string>();
}
RemoveMatchingKeys(t, name);
registeredKeys[t].Add(name);
}
RegisterType方法,这个方法根本没有考虑到多线程同步问题直接对registeredKeys进行Add操作,同样在RegisteredTypes属性中也没有进行同步处理。很显然读写异步的情况肯定会出现上面所说的异常了。
虽然可以通过在外部调用Regist和IsRegist方法进行同步处理,但是这样的话调用外层的Lock就比较麻烦而且比较分散,性能也可能有问题。