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 这种形式,我们不能显式地从这两个类派生。”