我們在編程的時(shí)候,有時(shí)會(huì)使用多線程來解決問題,比如你的程序需要在后臺(tái)處理一大堆數(shù)據(jù),但還要使用戶界面處于可操作狀態(tài);或者你的程序需要訪問一些外部資源如數(shù)據(jù)庫或網(wǎng)絡(luò)文件等。這些情況你都可以創(chuàng)建一個(gè)子線程去處理,然而,多線程不可避免地會(huì)帶來一個(gè)問題,就是線程同步的問題。如果這個(gè)問題處理不好,我們就會(huì)得到一些非預(yù)期的結(jié)果。
在網(wǎng)上也看過一些關(guān)于線程同步的文章,其實(shí)線程同步有好幾種方法,下面我就簡單的做一下歸納。
一、volatile關(guān)鍵字
volatile是最簡單的一種同步方法,當(dāng)然簡單是要付出代價(jià)的。它只能在變量一級(jí)做同步,volatile的含義就是告訴處理器, 不要將我放入工作內(nèi)存, 請(qǐng)直接在主存操作我。(【轉(zhuǎn)自www.bitsCN.com】)因此,當(dāng)多線程同時(shí)訪問該變量時(shí),都將直接操作主存,從本質(zhì)上做到了變量共享。
能夠被標(biāo)識(shí)為volatile的必須是以下幾種類型:(摘自MSDN)
如:


但volatile并不能實(shí)現(xiàn)真正的同步,因?yàn)樗牟僮骷?jí)別只停留在變量級(jí)別,而不是原子級(jí)別。如果是在單處理器系統(tǒng)中,是沒有任何問題的,變量在主存中沒有機(jī)會(huì)被其他人修改,因?yàn)橹挥幸粋€(gè)處理器,這就叫作processor Self-Consistency。但在多處理器系統(tǒng)中,可能就會(huì)有問題。每個(gè)處理器都有自己的data cach,而且被更新的數(shù)據(jù)也不一定會(huì)立即寫回到主存。所以可能會(huì)造成不同步,但這種情況很難發(fā)生,因?yàn)閏ach的讀寫速度相當(dāng)快,flush的頻率也相當(dāng)高,只有在壓力測試的時(shí)候才有可能發(fā)生,而且?guī)茁史浅7浅P ?/p>
二、lock關(guān)鍵字
lock是一種比較好用的簡單的線程同步方式,它是通過為給定對(duì)象獲取互斥鎖來實(shí)現(xiàn)同步的。它可以保證當(dāng)一個(gè)線程在關(guān)鍵代碼段的時(shí)候,另一個(gè)線程不會(huì)進(jìn)來,它只能等待,等到那個(gè)線程對(duì)象被釋放,也就是說線程出了臨界區(qū)。用法:


lock的參數(shù)必須是基于引用類型的對(duì)象,不要是基本類型像bool,int什么的,這樣根本不能同步,原因是lock的參數(shù)要求是對(duì)象,如果傳入int,勢必要發(fā)生裝箱操作,這樣每次lock的都將是一個(gè)新的不同的對(duì)象。最好避免使用public類型或不受程序控制的對(duì)象實(shí)例,因?yàn)檫@樣很可能導(dǎo)致死鎖。特別是不要使用字符串作為lock的參數(shù),因?yàn)樽址籆LR“暫留”,就是說整個(gè)應(yīng)用程序中給定的字符串都只有一個(gè)實(shí)例,因此更容易造成死鎖現(xiàn)象。建議使用不被“暫留”的私有或受保護(hù)成員作為參數(shù)。其實(shí)某些類已經(jīng)提供了專門用于被鎖的成員,比如Array類型提供SyncRoot,許多其它集合類型也都提供了SyncRoot。
所以,使用lock應(yīng)該注意以下幾點(diǎn):
1、如果一個(gè)類的實(shí)例是public的,最好不要lock(this)。因?yàn)槭褂媚愕念惖娜艘苍S不知道你用了lock,如果他new了一個(gè)實(shí)例,并且對(duì)這個(gè)實(shí)例上鎖,就很容易造成死鎖。
2、如果MyType是public的,不要lock(typeof(MyType))
3、永遠(yuǎn)也不要lock一個(gè)字符串
三、System.Threading.Interlocked
對(duì)于整數(shù)數(shù)據(jù)類型的簡單操作,可以用Interlocked類的成員來實(shí)現(xiàn)線程同步,存在于System.Threading命名空間。Interlocked類有以下方法:Increment,Decrement,Exchange和CompareExchange。使用Increment和Decrement可以保證對(duì)一個(gè)整數(shù)的加減為一個(gè)原子操作。Exchange方法自動(dòng)交換指定變量的值。CompareExchange方法組合了兩個(gè)操作:比較兩個(gè)值以及根據(jù)比較的結(jié)果將第三個(gè)值存儲(chǔ)在其中一個(gè)變量中。比較和交換操作也是按原子操作執(zhí)行的。如:


Output:

四、Monitor
Monitor類提供了與lock類似的功能,不過與lock不同的是,它能更好的控制同步塊,當(dāng)調(diào)用了Monitor的Enter(Object o)方法時(shí),會(huì)獲取o的獨(dú)占權(quán),直到調(diào)用Exit(Object o)方法時(shí),才會(huì)釋放對(duì)o的獨(dú)占權(quán),可以多次調(diào)用Enter(Object o)方法,只需要調(diào)用同樣次數(shù)的Exit(Object o)方法即可,Monitor類同時(shí)提供了TryEnter(Object o,[int])的一個(gè)重載方法,該方法嘗試獲取o對(duì)象的獨(dú)占權(quán),當(dāng)獲取獨(dú)占權(quán)失敗時(shí),將返回false。
但使用lock通常比直接使用Monitor更可取,一方面是因?yàn)?strong>lock更簡潔,另一方面是因?yàn)?strong>lock確保了即使受保護(hù)的代碼引發(fā)異常,也可以釋放基礎(chǔ)監(jiān)視器。這是通過finally中調(diào)用Exit來實(shí)現(xiàn)的。事實(shí)上,lock就是用Monitor類來實(shí)現(xiàn)的。下面兩段代碼是等效的:


關(guān)于用法,請(qǐng)參考下面的代碼:


當(dāng)線程1獲取了m_monitorObject對(duì)象獨(dú)占權(quán)時(shí),線程2嘗試調(diào)用TryEnter(m_monitorObject),此時(shí)會(huì)由于無法獲取獨(dú)占權(quán)而返回false,輸出信息如下:
另外,Monitor還提供了三個(gè)靜態(tài)方法Monitor.Pulse(Object o),Monitor.PulseAll(Object o)和Monitor.Wait(Object o ) ,用來實(shí)現(xiàn)一種喚醒機(jī)制的同步。關(guān)于這三個(gè)方法的用法,可以參考MSDN,這里就不詳述了。
五、Mutex
在使用上,Mutex與上述的Monitor比較接近,不過Mutex不具備Wait,Pulse,PulseAll的功能,因此,我們不能使用Mutex實(shí)現(xiàn)類似的喚醒的功能。不過Mutex有一個(gè)比較大的特點(diǎn),Mutex是跨進(jìn)程的,因此我們可以在同一臺(tái)機(jī)器甚至遠(yuǎn)程的機(jī)器上的多個(gè)進(jìn)程上使用同一個(gè)互斥體。盡管Mutex也可以實(shí)現(xiàn)進(jìn)程內(nèi)的線程同步,而且功能也更強(qiáng)大,但這種情況下,還是推薦使用Monitor,因?yàn)镸utex類是win32封裝的,所以它所需要的互操作轉(zhuǎn)換更耗資源。
六、ReaderWriterLock
在考慮資源訪問的時(shí)候,慣性上我們會(huì)對(duì)資源實(shí)施lock機(jī)制,但是在某些情況下,我們僅僅需要讀取資源的數(shù)據(jù),而不是修改資源的數(shù)據(jù),在這種情況下獲取資源的獨(dú)占權(quán)無疑會(huì)影響運(yùn)行效率,因此.Net提供了一種機(jī)制,使用ReaderWriterLock進(jìn)行資源訪問時(shí),如果在某一時(shí)刻資源并沒有獲取寫的獨(dú)占權(quán),那么可以獲得多個(gè)讀的訪問權(quán),單個(gè)寫入的獨(dú)占權(quán),如果某一時(shí)刻已經(jīng)獲取了寫入的獨(dú)占權(quán),那么其它讀取的訪問權(quán)必須進(jìn)行等待,參考以下代碼:


在程序中,我們啟動(dòng)兩個(gè)線程獲取m_int的讀取訪問權(quán),使用一個(gè)線程獲取m_int的寫入獨(dú)占權(quán),執(zhí)行代碼后,輸出如下:

可以看到,當(dāng)WriterThread獲取到寫入獨(dú)占權(quán)后,任何其它讀取的線程都必須等待,直到WriterThread釋放掉寫入獨(dú)占權(quán)后,才能獲取到數(shù)據(jù)的訪問權(quán),應(yīng)該注意的是,上述打印信息很明顯顯示出,可以多個(gè)線程同時(shí)獲取數(shù)據(jù)的讀取權(quán),這從ReadThread1和ReadThread2的信息交互輸出可以看出。
七、SynchronizationAttribute
當(dāng)我們確定某個(gè)類的實(shí)例在同一時(shí)刻只能被一個(gè)線程訪問時(shí),我們可以直接將類標(biāo)識(shí)成Synchronization的,這樣,CLR會(huì)自動(dòng)對(duì)這個(gè)類實(shí)施同步機(jī)制,實(shí)際上,這里面涉及到同步域的概念,當(dāng)類按如下設(shè)計(jì)時(shí),我們可以確保類的實(shí)例無法被多個(gè)線程同時(shí)訪問 1).在類的聲明中,添加System.Runtime.Remoting.Contexts.Synchronization
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注