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數組
[StructLayout(LayoutKind.Sequential)]
private struct bucket
{
public object key;
public object val;
public int hash_coll;
}
而Dictionary<TKey,TValue>用來存放資料的是Entry數組(裡面多了一項next,同學們可以自己想想他用來做什麼^_^)
[StructLayout(LayoutKind.Sequential)]
private struct Entry
{
public int hashCode;
public int next;
public TKey key;
public TValue value;
}
既然是數組,那麼這個數組應該申請多大長度呢?即便你在建構函式中進行了指定,.net也不會完全以你指定的為準,而是會從素數表裡面找一個素數:
private void Initialize(int capacity)
{
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 內部有一個素數數組,記錄了常用到的素數。如下所示:
static HashHelpers()
{
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 這種形式,我們不能顯式地從這兩個類派生。”