1. 上午老师提到一个thread必须隶属于某个appDomain,我觉得有点疑惑。简单在网上找了找,下面的关于soft thread和hard thread的阐述是我比较信服的:
AppDomain是个静态概念,只是限定了对象的边界;线程是个动态概念,它可以运行在不同的AppDomain。
一个AppDomain内可以创建多个线程,但是不能限定这些线程只能在本AppDomain内执行代码。
CLR中的System.Threading.Thread对象其实是个soft thread,它并不能被操作系统识别;操作系统能识别的是hard thread。
一个soft thread只属于一个AppDomain,穿越AppDomain的是hard thread。当hard thread访问到某个AppDomain时,一个AppDomain就会为之产生一个soft thread。
hard thread有thread local storage(TLS),这个存储区被CLR用来存储这个hard thread当前对应的AppDomain引用以及soft
thread引用。当一个hard thread穿越到另外一个AppDomain时,TLS中的这些引用也会改变。
2.关于程序集的“热插拔”
杨老师提到过保持程序集对外接口稳定可以实现程序集的“热插拔”(即在不停止主应用程序的前提下,动态替换单个程序集)。下来查了一下,这种替换应该是 有很多条件的,因为.net不支持对单个程序集的Unload()操作,你必须把要替换的程序集放置在一个单独的AppDomain中运行,替换程序集的时候要杀掉这个AppDomain,在一个新的AppDomain中Load新版本的程序集。
(AssemblyLoader.RemoteLoader)domain.CreateInstanceFromAndUnwrap(
"AssemblyLoader.dll","AssemblyLoader.RemoteLoader");
所以,对这个程序集中方法的调用很多都将是跨边界的远程调用,性能上应该很成问题吧?有没有哪位同志以前在项目中这样用过?
3.哈希/字典的底层结构是什么?其实就是数组,而根据key利用散列算法计算出来的hashcode自然成为了数组的下标。
Hashtable用来存放数据的是bucket数组
private struct bucket
{
public object key;
public object val;
public int hash_coll;
}
而Dictionary<TKey,TValue>用来存放数据的是Entry数组(里面多了一项next,同学们可以自己想想他用来做什么^_^)
private struct Entry
{
public int hashCode;
public int next;
public TKey key;
public TValue value;
}
既然是数组,那么这个数组应该申请多大长度呢?即便你在构造函数中进行了指定,.net也不会完全以你指定的为准,而是会从素数表里面找一个素数:
{
int prime = HashHelpers.GetPrime(capacity);
this .buckets = new int [prime];
for ( int i = 0 ; i < this .buckets.Length; i ++ )
{
this .buckets[i] = - 1 ;
}
this .entries = new Entry < TKey, TValue > [prime];
this .freeList = - 1 ;
}
那么如果你插入的数据量很大,预先申请的数组不够用了怎么办?扩容。
扩容是个耗时非常惊人的内部操作,Hashtable 之所以写入效率仅为读取效率的 1/10 数量级, 频繁的扩容是一个因素。当进行扩容时,散列表内部要重新 new 一个更大的数组,然后把原来数组的内容拷贝到新数组,并进行重新散列。如何 new 这个更大的数组也有讲究。散列表的初始容量一般来讲是个素数。当扩容时,新数组的大小会设置成原数组双倍大小的相近的一个素数。为了避免生成素数的额外开销,.NET 内部有一个素数数组,记录了常用到的素数。如下所示:
{
primes = new int [] {
3 , 7 , 11 , 0x11 , 0x17 , 0x1d , 0x25 , 0x2f , 0x3b , 0x47 , 0x59 , 0x6b , 0x83 , 0xa3 , 0xc5 , 0xef ,
0x125 , 0x161 , 0x1af , 0x209 , 0x277 , 0x2f9 , 0x397 , 0x44f , 0x52f , 0x63d , 0x78b , 0x91d , 0xaf1 , 0xd2b , 0xfd1 , 0x12fd ,
0x16cf , 0x1b65 , 0x20e3 , 0x2777 , 0x2f6f , 0x38ff , 0x446f , 0x521f , 0x628d , 0x7655 , 0x8e01 , 0xaa6b , 0xcc89 , 0xf583 , 0x126a7 , 0x1619b ,
0x1a857 , 0x1fd3b , 0x26315 , 0x2dd67 , 0x3701b , 0x42023 , 0x4f361 , 0x5f0ed , 0x72125 , 0x88e31 , 0xa443b , 0xc51eb , 0xec8c1 , 0x11bdbf , 0x154a3f , 0x198c4f ,
0x1ea867 , 0x24ca19 , 0x2c25c1 , 0x34fa1b , 0x3f928f , 0x4c4987 , 0x5b8b6f , 0x6dda89
};
}
4. Delegate的序列化
今天杨老师还谈到了代理序列化的问题。我们都知道代理后面可以挂一个很长的委托链,那序列化的时候这个委托链怎么办?google了一下,答案竟然是整个委托链都要被序列化,这就要求委托链上的每个对象都是可序列化的(要求很苛刻阿…)。所以,delegate的序列化要慎用。对了,还查到一个有趣的结果,以前没注意到:
“delegate 开头是小写的 d,如果换成大写开头的 Delegate,那差别可就大了,简直是孙子和爷爷的区别。为什么说是孙子和爷爷呢?因为 delegate 仅仅是 C# 的关键字,表示一个继承自 System.MulticastDelegate 的具体委托类,而 Delegate 却是 System.MulticastDelegate 的父类,这还不是孙子和爷爷吗。另外,Delegate 和 System.MulticastDelegate 都是抽象类,只有编译器才可以从此类派生。也就是说,除了用 delegate 这种形式,我们不能显式地从这两个类派生。”