上次寫了個文章記錄了一些在UI開發上的關於非同步呼叫的收穫,上次的問題之所以用到非同步呼叫就是因為需要開啟一個背景工作執行緒來做一些事情,並保證安全執行緒。問題解決之後就開始在SDK,MSDN,和我的一個高手朋友的blog裡找尋一些有關於線程同步問題的內容,看了一些東西,也自己寫了一些東西測了一下,還是瞭解了一些原來概念上不是很清楚的內容。
上次用到需要同步的對象是一個Hashtable,採用的是簡單到不能再簡單的加鎖的辦法。但是,每次處理都要加鎖未免太費事了,有沒有完全安全執行緒的對象呢?在SDK中我找到了答案。通過Hashtable.
Synchronized()方法,可以封裝一個Hashtable的同步版本供我們使用,具體是如何?的同步版本,通過Reflector進行一下察看就很清楚了,在這個方法中New了一個新對象並返回:
public static Hashtable Synchronized(Hashtable table) { if (table == null) { throw new ArgumentNullException("table"); } return new Hashtable.SyncHashtable(table); } |
而這個新對象是一個派生自hashtalbe的類(SyncHashtable)對象,在這個類中,.net已經對所有的屬性和方法進行了加鎖,這樣一來我們在使用這個對象的時候就不必自己再去作加鎖的工作了。當然了這個類是一個內部的私人類,我們無法通過new的方式建立,只能通過Synchronized方法來擷取。
這樣一來就又有了一個問題,那是不是我們每聲明一個Hashtable,都用Synchronized方法來封裝一下呢?當然不是,首先對於單線程來說同步是沒有意義的,其次即使是多線程程式如果我們能夠非常確定hashtalbe的讀取與寫入不會產生衝突,那我們也沒有必要去同步它,畢竟加鎖和解鎖也是有效能損耗的。如果只是簡單的在一次使用中需要同步僅僅採用手動的加鎖就ok了,但是如果多處用到了這個Hashtable,那麼在聲明的時候就進行一次同步封裝還是有必要的,畢竟人的記憶力是有限的,說不定什麼地方就由於沒有進行同步處理而出錯。
還有一個問題也請教了朋友找到了答案。就是對於加鎖的對象的選取。我加鎖的對象就是Hashtable這個對象本身,但是在SDK中卻看到了這樣的代碼:
Hashtable myCollection = new Hashtable();
lock( myCollection.SyncRoot ) {
foreach ( Object item in myCollection )
{
// Insert your code here.
}
SyncRoot是何許人也?SDK中給出的定義是:擷取可用於同步對Hashtable的訪問的對象。上面整了一個返回同步封裝,這又來了一個同步訪問的對象。有點暈。不,那是相當的暈。這個時候Reflector又再次顯示了它無可替代了威力。
首先找到Hashtable,然後就是SyncRoot屬性,不看不要緊,一看更傻了:
public virtual object SyncRoot { get { return this; } } |
返回自己?什麼概念?最要命的是SDK有這麼一段話:
"同步代碼必須在 Hashtable 的 SyncRoot 上執行操作,而不是直接在 Hashtable 上執行操作。這確保了從其他對象派生的集合的正確操作。特別是,它維護了與其他線程的正確同步,這些線程可能同時正在修改該 Hashtable 對象。"
不能對自身加鎖,但是卻通過對返回對象自身的一個屬性加鎖,初學者應該能夠體會我當時的無助與憤怒。但是我馬上從抓狂的狀態下恢複了回來,將自己的精神從對一家叫做微微有點軟的公司的憤慨中轉回到問題本身。公司的名字雖然叫微軟,但是做的東西向來還算堅挺(大部分,而且要求不要過於苛刻),因此我很快的意識到問題還是出在了我自身(每次都不願意接受這個殘酷的事實)。難道僅僅是為了和其他實現了ICollection的集合類在加鎖的時候形成統一的形式,當然也應該有這個原因,但絕非核心因素,又向老朋友求助了(臉皮還真是厚啊,我們倆不是一個數量級的,不知道每次被我問在他看來不算問題的問題是什麼感受?鄙視?我忍,求知無罪)通過他的解釋和又查閱了一些資料,初步對加鎖的對象有了一些認識。
首先,加鎖的對象必須是參考型別的,這無可厚非。
其次,實際上對於加鎖的對象只要是個參考型別的對象(並不需要是需要同步的對象本身,甚至可以new一個object對象來用)就可以滿足最基本線程同步的要求。看來在這裡我犯了一個現在看來很傻的錯誤,想當然的認為同步哪個對象就需要鎖哪個對象,事實又一次證明了我的想象力的豐富在某些時候並不是什麼優點。
最後,關於SyncRoot屬性的使用我朋友的解釋是,當我們需要對對象的同步訪問進行一些自訂的控制是(例如:對於集合的讀取器我們不加鎖,只對編寫器代碼進行對象的加鎖),使用對象的屬性就可以在屬性的實現代碼中進行控制,而直接鎖定對象本身是無法實現對同步訪問的控制的。
明白了不少東西,但是想在自己的實際工作中應用起來可能還需要一些時間,畢竟我線在的任務涉及的大量線程的是沒有的,即使有,再線程同步的時候也需要加小心,畢竟無意義的加鎖是會使系統變得很慢,當變得相當的慢的時候,相信使用者體驗就和死結沒有什麼區別了。