我只簡(jiǎn)單列舉幾種常用的方法,詳細(xì)可參考.Net多線程總結(jié)(一)
一)使用Thread類
二)使用Delegate.BeginInvoke
三)使用ThreadPool.QueueworkItem
下面兩條來(lái)自于http://www.cnblogs.com/tonyman/archive/2007/09/13/891912.html
必須要了解,執(zhí)行.NET應(yīng)用的線程實(shí)際上仍然是Windows線程。但是,當(dāng)某個(gè)線程被CLR所知時(shí),我們將它稱為受托管的線程。具體來(lái)說(shuō),由受托管的代碼創(chuàng)建出來(lái)的線程就是受托管的線程。如果一個(gè)線程由非托管的代碼所創(chuàng)建,那么它就是非托管的線程。不過(guò),一旦該線程執(zhí)行了受托管的代碼它就變成了受托管的線程。
一個(gè)受托管的線程和非托管的線程的區(qū)別在于,CLR將創(chuàng)建一個(gè)System.Threading.Thread類的實(shí)例來(lái)代表并操作前者。在內(nèi)部實(shí)現(xiàn)中,CLR將一個(gè)包含了所有受托管線程的列表保存在一個(gè)叫做ThreadStore地方。
CLR確保每一個(gè)受托管的線程在任意時(shí)刻都在一個(gè)AppDomain中執(zhí)行,但是這并不代表一個(gè)線程將永遠(yuǎn)處在一個(gè)AppDomain中,它可以隨著時(shí)間的推移轉(zhuǎn)到其他的AppDomain中。
從安全的角度來(lái)看,一個(gè)受托管的線程的主用戶與底層的非托管線程中的Windows主用戶是無(wú)關(guān)的。
經(jīng)??吹矫麨锽eginXXX和EndXXX的方法,他們是做什么用的
這是.net的一個(gè)異步方法名稱規(guī)范
.Net在設(shè)計(jì)的時(shí)候?yàn)楫惒骄幊淘O(shè)計(jì)了一個(gè)異步編程模型(APM),這個(gè)模型不僅是使用.NET的開發(fā)人員使用,.Net內(nèi)部也頻繁用到,比如所有的Stream就有BeginRead,EndRead,Socket,WebRequet,SqlCommand都運(yùn)用到了這個(gè)模式,一般來(lái)講,調(diào)用BegionXXX的時(shí)候,一般會(huì)啟動(dòng)一個(gè)異步過(guò)程去執(zhí)行一個(gè)操作,EndEnvoke可以接收這個(gè)異步操作的返回,當(dāng)然如果異步操作在EndEnvoke調(diào)用的時(shí)候還沒有執(zhí)行完成,EndInvoke會(huì)一直等待異步操作完成或者超時(shí)
.Net的異步編程模型(APM)一般包含BeginXXX,EndXXX,IAsyncResult這三個(gè)元素,BeginXXX方法都要返回一個(gè)IAsyncResult,而EndXXX都需要接收一個(gè)IAsyncResult作為參數(shù),他們的函數(shù)簽名模式如下
IAsyncResult BeginXXX(...);
<返回類型> EndXXX(IAsyncResult ar);
BeginXXX和EndXXX中的XXX,一般都對(duì)應(yīng)一個(gè)同步的方法,比如FileStream的Read方法是一個(gè)同步方法,相應(yīng)的BeginRead(),EndRead()就是他的異步版本,HttpRequest有GetResponse來(lái)同步接收一個(gè)響應(yīng),也提供了BeginGetResponse和EndGetResponse這個(gè)異步版本,而IAsynResult是二者聯(lián)系的紐帶,只有把BeginXXX所返回的IAsyncResult傳給對(duì)應(yīng)的EndXXX,EndXXX才知道需要去接收哪個(gè)BeginXXX發(fā)起的異步操作的返回值。
這個(gè)模式在實(shí)際使用時(shí)稍顯繁瑣,雖然原則上我們可以隨時(shí)調(diào)用EndInvoke來(lái)獲得返回值,并且可以同步多個(gè)線程,但是大多數(shù)情況下當(dāng)我們不需要同步很多線程的時(shí)候使用回調(diào)是更好的選擇,在這種情況下三個(gè)元素中的IAsynResult就顯得多余,我們一不需要用其中的線程完結(jié)標(biāo)志來(lái)判斷線程是否成功完成(回調(diào)的時(shí)候線程應(yīng)該已經(jīng)完成了),二不需要他來(lái)傳遞數(shù)據(jù),因?yàn)閿?shù)據(jù)可以寫在任何變量里,并且回調(diào)時(shí)應(yīng)該已經(jīng)填充,所以可以看到微軟在新的.Net Framework中已經(jīng)加強(qiáng)了對(duì)回調(diào)事件的支持,這總模型下,典型的回調(diào)程序應(yīng)該這樣寫
a.DoWork+=new SomeEventHandler(Caculate);
a.CallBack+=new SomeEventHandler(callback);
a.Run();
(注:我上面講的是普遍的用法,然而BeginXXX,EndXXX僅僅是一種模式,而對(duì)這個(gè)模式的實(shí)現(xiàn)完全取決于使用他的開發(fā)人員,具體實(shí)現(xiàn)的時(shí)候你可以使用另外一個(gè)線程來(lái)實(shí)現(xiàn)異步,也可能使用硬件的支持來(lái)實(shí)現(xiàn)異步,甚至可能根本和異步?jīng)]有關(guān)系(盡管幾乎沒有人會(huì)這樣做)-----比如直接在Beginxxx里直接輸出一個(gè)"Helloworld",如果是這種極端的情況,那么上面說(shuō)的一切都是廢話,所以上面的探討并不涉及內(nèi)部實(shí)現(xiàn),只是告訴大家微軟的模式,和框架中對(duì)這個(gè)模式的經(jīng)典實(shí)現(xiàn))
有一句話總結(jié)的很好:多線程是實(shí)現(xiàn)異步的一種手段和工具
我們通常把多線程和異步等同起來(lái),實(shí)際是一種誤解,在實(shí)際實(shí)現(xiàn)的時(shí)候,異步有許多種實(shí)現(xiàn)方法,我們可以用進(jìn)程來(lái)做異步,或者使用纖程,或者硬件的一些特性,比如在實(shí)現(xiàn)異步IO的時(shí)候,可以有下面兩個(gè)方案:
1)可以通過(guò)初始化一個(gè)子線程,然后在子線程里進(jìn)行IO,而讓主線程順利往下執(zhí)行,當(dāng)子線程執(zhí)行完畢就回調(diào)
2)也可以根本不使用新線程,而使用硬件的支持(現(xiàn)在許多硬件都有自己的處理器),來(lái)實(shí)現(xiàn)完全的異步,這是我們只需要將IO請(qǐng)求告知硬件驅(qū)動(dòng)程序,然后迅速返回,然后等著硬件IO就緒通知我們就可以了
實(shí)際上DotNet Framework里面就有這樣的例子,當(dāng)我們使用文件流的時(shí)候,如果制定文件流屬性為同步,則使用BeginRead進(jìn)行讀取時(shí),就是用一個(gè)子線程來(lái)調(diào)用同步的Read方法,而如果指定其為異步,則同樣操作時(shí)就使用了需要硬件和操作系統(tǒng)支持的所謂IOCP的機(jī)制
我的多線程WinForm程序老是拋出InvalidOperationException ,怎么解決?
在WinForm中使用多線程時(shí),常常遇到一個(gè)問(wèn)題,當(dāng)在子線程(非UI線程)中修改一個(gè)控件的值:比如修改進(jìn)度條進(jìn)度,時(shí)會(huì)拋出如下錯(cuò)誤
Cross-thread operation not valid: Control 'XXX' accessed from a thread other than the thread it was created on.
在VS2005或者更高版本中,只要不是在控件的創(chuàng)建線程(一般就是指UI主線程)上訪問(wèn)控件的屬性就會(huì)拋出這個(gè)錯(cuò)誤,解決方法就是利用控件提供的Invoke和BeginInvoke把調(diào)用封送回UI線程,也就是讓控件屬性修改在UI線程上執(zhí)行,下面列出會(huì)報(bào)錯(cuò)的代碼和他的修改版本
上面加粗的地方在debug的時(shí)候會(huì)報(bào)錯(cuò),最直接的修改方法是修改Calculate這個(gè)方法如下
這樣就ok了,但是最漂亮的方法是不去修改Calculate,而去修改CalcFinished這個(gè)方法,因?yàn)槌绦蚶镎{(diào)用這個(gè)方法的地方可能很多,由于加了是否需要封送的判斷,這樣修改還能提高非跨線程調(diào)用時(shí)的性能
上面的做法用到了Control的一個(gè)屬性InvokeRequired(這個(gè)屬性是可以在其他線程里訪問(wèn)的),這個(gè)屬性表明調(diào)用是否來(lái)自另非UI線程,如果是,則使用BeginInvoke來(lái)調(diào)用這個(gè)函數(shù),否則就直接調(diào)用,省去線程封送的過(guò)程
Invoke,BeginInvoke干什么用的,內(nèi)部是怎么實(shí)現(xiàn)的?
這兩個(gè)方法主要是讓給出的方法在控件創(chuàng)建的線程上執(zhí)行
Invoke使用了Win32API的SendMessage,
UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);
BeginInvoke使用了Win32API的PostMessage
UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);
這兩個(gè)方法向UI線程的消息隊(duì)列中放入一個(gè)消息,當(dāng)UI線程處理這個(gè)消息時(shí),就會(huì)在自己的上下文中執(zhí)行傳入的方法,換句話說(shuō)凡是使用BeginInvoke和Invoke調(diào)用的線程都是在UI主線程中執(zhí)行的,所以如果這些方法里涉及一些靜態(tài)變量,不用考慮加鎖的問(wèn)題
不是,只有創(chuàng)建了窗體對(duì)象的線程才會(huì)有消息隊(duì)列(下面給出<Windows 核心編程>關(guān)于這一段的描述)
當(dāng)一個(gè)線程第一次被建立時(shí),系統(tǒng)假定線程不會(huì)被用于任何與用戶相關(guān)的任務(wù)。這樣可以減少線程對(duì)系統(tǒng)資源的要求。但是,一旦這個(gè)線程調(diào)用一個(gè)與圖形用戶界面有關(guān)的函數(shù)(例如檢查它的消息隊(duì)列或建立一個(gè)窗口),系統(tǒng)就會(huì)為該線程分配一些另外的資源,以便它能夠執(zhí)行與用戶界面有關(guān)的任務(wù)。特別是,系統(tǒng)分配一個(gè)T H R E A D I N F O結(jié)構(gòu),并將這個(gè)數(shù)據(jù)結(jié)構(gòu)與線程聯(lián)系起來(lái)。
這個(gè)T H R E A D I N F O結(jié)構(gòu)包含一組成員變量,利用這組成員,線程可以認(rèn)為它是在自己獨(dú)占的環(huán)境中運(yùn)行。T H R E A D I N F O是一個(gè)內(nèi)部的、未公開的數(shù)據(jù)結(jié)構(gòu),用來(lái)指定線程的登記消息隊(duì)列(posted-message queue)、發(fā)送消息隊(duì)列( send-message queue)、應(yīng)答消息隊(duì)列( r e p l y -message queue)、虛擬輸入隊(duì)列(virtualized-input queue)、喚醒標(biāo)志(wake flag)、以及用來(lái)描述線程局部輸入狀態(tài)的若干變量。圖2 6 - 1描述了T H R E A D I N F O結(jié)構(gòu)和與之相聯(lián)系的三個(gè)線程。
在vs2003下,使用子線程調(diào)用ui線程創(chuàng)建的控件的屬性是不會(huì)有問(wèn)題的,但是編譯的時(shí)候會(huì)出現(xiàn)警告,但是vs2005及以上版本就會(huì)有這樣的問(wèn)題,下面是msdn上的描述
"當(dāng)您在 Visual Studio 調(diào)試器中運(yùn)行代碼時(shí),如果您從一個(gè)線程訪問(wèn)某個(gè) UI 元素,而該線程不是創(chuàng)建該 UI 元素時(shí)所在的線程,則會(huì)引發(fā) InvalidOperationException。調(diào)試器引發(fā)該異常以警告您存在危險(xiǎn)的編程操作。UI 元素不是線程安全的,所以只應(yīng)在創(chuàng)建它們的線程上進(jìn)行訪問(wèn)"
從上面可以看出,這個(gè)異常實(shí)際是debugger耍的花招,也就是說(shuō),如果你直接運(yùn)行程序的exe文件,或者利用運(yùn)行而不調(diào)試(Ctrl+F5)來(lái)運(yùn)行你的程序,是不會(huì)拋出這樣的異常的.大概ms發(fā)現(xiàn)v2003的警告對(duì)廣大開發(fā)者不起作用,所以用了一個(gè)比較狠一點(diǎn)的方法.
不過(guò)問(wèn)題依然存在:既然這樣設(shè)計(jì)的原因主要是因?yàn)榭丶闹捣蔷€程安全,那么DotNet framework中非線程安全的類千千萬(wàn)萬(wàn),為什么偏偏跨線程修改Control的屬性會(huì)有這樣嚴(yán)格的限制策略呢?
這個(gè)問(wèn)題我還回答不好,希望博友們能夠予以補(bǔ)充
有沒有什么辦法可以簡(jiǎn)化WinForm多線程的開發(fā)
使用backgroundworker,使用這個(gè)組建可以避免回調(diào)時(shí)的Invoke和BeginInvoke,并且提供了許多豐富的方法和事件
參見.Net多線程總結(jié)(二)-BackgroundWorker,我在這里不再贅訴
作用是減小線程創(chuàng)建和銷毀的開銷
創(chuàng)建線程涉及用戶模式和內(nèi)核模式的切換,內(nèi)存分配,dll通知等一系列過(guò)程,線程銷毀的步驟也是開銷很大的,所以如果應(yīng)用程序使用了完一個(gè)線程,我們能把線程暫時(shí)存放起來(lái),以備下次使用,就可以減小這些開銷
所有進(jìn)程使用一個(gè)共享的線程池,還是每個(gè)進(jìn)程使用獨(dú)立的線程池?
每個(gè)進(jìn)程都有一個(gè)線程池,一個(gè)Process中只能有一個(gè)實(shí)例,它在各個(gè)應(yīng)用程序域(AppDomain)是共享的,.Net2.0 中默認(rèn)線程池的大小為工作線程25個(gè),IO線程1000個(gè),有一個(gè)比較普遍的誤解是線程池中會(huì)有1000個(gè)線程等著你去取,其實(shí)不然, ThreadPool僅僅保留相當(dāng)少的線程,保留的線程可以用SetMinThread這個(gè)方法來(lái)設(shè)置,當(dāng)程序的某個(gè)地方需要?jiǎng)?chuàng)建一個(gè)線程來(lái)完成工作時(shí),而線程池中又沒有空閑線程時(shí),線程池就會(huì)負(fù)責(zé)創(chuàng)建這個(gè)線程,并且在調(diào)用完畢后,不會(huì)立刻銷毀,而是把他放在池子里,預(yù)備下次使用,同時(shí)如果線程超過(guò)一定時(shí)間沒有被使用,線程池將會(huì)回收線程,所以線程池里存在的線程數(shù)實(shí)際是個(gè)動(dòng)態(tài)的過(guò)程
當(dāng)我首次看到線程池的時(shí)候,腦袋里的第一個(gè)念頭就是給他設(shè)定一個(gè)最大值,然而當(dāng)我們查看ThreadPool的SetMaxThreads文檔時(shí)往往會(huì)看到一條警告:不要手動(dòng)更改線程池的大小,這是為什么呢?
其實(shí)無(wú)論FileStream的異步讀寫,異步發(fā)送接受Web請(qǐng)求,甚至使用delegate的beginInvoke都會(huì)默認(rèn)調(diào)用 ThreadPool,也就是說(shuō)不僅你的代碼可能使用到線程池,框架內(nèi)部也可能使用到,更改的后果影響就非常大,特別在iis中,一個(gè)應(yīng)用程序池中的所有 WebApplication會(huì)共享一個(gè)線程池,對(duì)最大值的設(shè)定會(huì)帶來(lái)很多意想不到的麻煩
線程池有一個(gè)方法可以讓我們看到線程池中可用的線程數(shù)量:GetAvaliableThread(out workerThreadCount,out iocompletedThreadCount),對(duì)于我來(lái)說(shuō),第一次看到這個(gè)函數(shù)的參數(shù)時(shí)十分困惑,因?yàn)槲移谕@個(gè)函數(shù)直接返回一個(gè)整形,表明還剩多少線程,這個(gè)函數(shù)居然一次返回了兩個(gè)變量.
原來(lái)線程池里的線程按照公用被分成了兩大類:工作線程和IO線程,或者IO完成線程,前者用于執(zhí)行普通的操作,后者專用于異步IO,比如文件和網(wǎng)絡(luò)請(qǐng)求,注意,分類并不說(shuō)明兩種線程本身有差別,線程就是線程,是一種執(zhí)行單元,從本質(zhì)上來(lái)講都是一樣的,線程池這樣分類,舉例來(lái)說(shuō),就好像某施工工地現(xiàn)在有1000把鐵鍬,規(guī)定其中25把給后勤部門用,其他都給施工部門,施工部門需要大量使用鐵鍬來(lái)挖地基(例子土了點(diǎn),不過(guò)說(shuō)明問(wèn)題還是有效的),后勤部門用鐵鍬也就是鏟鏟雪,鏟鏟垃圾,給工人師傅修修臨時(shí)住房,所以用量不大,顯然兩個(gè)部門的鐵鍬本身沒有區(qū)別,但是這樣的劃分就為管理兩個(gè)部門的鐵鍬提供了方便
線程池中兩種線程分別在什么情況下被使用,二者工作原理有什么不同?
下面這個(gè)例子直接說(shuō)明了二者的區(qū)別,我們用一個(gè)流讀出一個(gè)很大的文件(大一點(diǎn)操作的時(shí)間長(zhǎng),便于觀察),然后用另一個(gè)輸出流把所讀出的文件的一部分寫到磁盤上
我們用兩種方法創(chuàng)建輸出流,分別是
創(chuàng)建了一個(gè)異步的流(注意構(gòu)造函數(shù)最后那個(gè)true)
FileStream outputfs=new FileStream(writepath, FileMode.Create, FileAccess.Write, FileShare.None,256,true);
創(chuàng)建了一個(gè)同步的流
FileStream outputfs = File.OpenWrite(writepath);
然后在寫文件期間查看線程池的狀況
輸出結(jié)果
異步流
Worker: 500; IO: 1000
Worker: 500; IO: 999
同步流
Worker: 500; IO: 1000
Worker: 499; IO: 1000
這兩個(gè)構(gòu)造函數(shù)創(chuàng)建的流都可以使用BeginWrite來(lái)異步寫數(shù)據(jù),但是二者行為不同,當(dāng)使用同步的流進(jìn)行異步寫時(shí),通過(guò)回調(diào)的輸出我們可以看到,他使用的是工作線程,而非IO線程,而異步流使用了IO線程而非工作線程
其實(shí)當(dāng)沒有制定異步屬性的時(shí)候,.Net實(shí)現(xiàn)異步IO是用一個(gè)子線程調(diào)用fs的同步Write方法來(lái)實(shí)現(xiàn)的,這時(shí)這個(gè)子線程會(huì)一直阻塞直到調(diào)用完成.這個(gè)子線程其實(shí)就是線程池的一個(gè)工作線程,所以我們可以看到,同步流的異步寫回調(diào)中輸出的工作線程數(shù)少了一,而使用異步流,在進(jìn)行異步寫時(shí),采用了 IOCP方法,簡(jiǎn)單說(shuō)來(lái),就是當(dāng)BeginWrite執(zhí)行時(shí),把信息傳給硬件驅(qū)動(dòng)程序,然后立即往下執(zhí)行(注意這里沒有額外的線程),而當(dāng)硬件準(zhǔn)備就緒, 就會(huì)通知線程池,使用一個(gè)IO線程來(lái)讀取
沒有提供方法控制加入線程池的線程:一旦加入線程池,我們沒有辦法掛起,終止這些線程,唯一可以做的就是等他自己執(zhí)行
1)不能為線程設(shè)置優(yōu)先級(jí)
2)一個(gè)Process中只能有一個(gè)實(shí)例,它在各個(gè)AppDomain是共享的。ThreadPool只提供了靜態(tài)方法,不僅我們自己添加進(jìn)去的WorkItem使用這個(gè)Pool,而且.net framework中那些BeginXXX、EndXXX之類的方法都會(huì)使用此Pool。
3)所支持的Callback不能有返回值。WaitCallback只能帶一個(gè)object類型的參數(shù),沒有任何返回值。
4)不適合用在長(zhǎng)期執(zhí)行某任務(wù)的場(chǎng)合。我們常常需要做一個(gè)Service來(lái)提供不間斷的服務(wù)(除非服務(wù)器down掉),但是使用ThreadPool并不合適。
下面是另外一個(gè)網(wǎng)友總結(jié)的什么不需要使用線程池,我覺得挺好,引用下來(lái)
如果您需要使一個(gè)任務(wù)具有特定的優(yōu)先級(jí)。
如果您具有可能會(huì)長(zhǎng)時(shí)間運(yùn)行(并因此阻塞其他任務(wù))的任務(wù)。
如果您需要將線程放置到單線程單元中(所有 ThreadPool 線程均處于多線程單元中)。
如果您需要與該線程關(guān)聯(lián)的穩(wěn)定標(biāo)識(shí)。例如,您應(yīng)使用一個(gè)專用線程來(lái)中止該線程、將其掛起或按名稱發(fā)現(xiàn)它。
CLR怎樣實(shí)現(xiàn)lock(obj)鎖定?
從原理上講,lock和Syncronized Attribute都是用Moniter.Enter實(shí)現(xiàn)的,比如如下代碼
在編譯時(shí),會(huì)被編譯為類似
而[MethodImpl(MethodImplOptions.Synchronized)]標(biāo)記為同步的方法會(huì)在編譯時(shí)被lock(this)語(yǔ)句所環(huán)繞
所以我們只簡(jiǎn)單探討Moniter.Enter的實(shí)現(xiàn)
(注:DotNet并非使用Win32API的CriticalSection來(lái)實(shí)現(xiàn)Moniter.Enter,不過(guò)他為托管對(duì)象提供了一個(gè)類似的結(jié)構(gòu)叫做Syncblk)
每個(gè)對(duì)象實(shí)例頭部都有一個(gè)指針,這個(gè)指針指向的結(jié)構(gòu),包含了對(duì)象的鎖定信息,當(dāng)?shù)谝淮问褂肕oniter.Enter(obj)時(shí),這個(gè)obj對(duì)象的鎖定結(jié)構(gòu)就會(huì)被初時(shí)化,第二次調(diào)用Moniter.Enter時(shí),會(huì)檢驗(yàn)這個(gè)object的鎖定結(jié)構(gòu),如果鎖沒有被釋放,則調(diào)用會(huì)阻塞
WaitHandle是Mutex,Semaphore,EventWaitHandler,AutoResetEvent,ManualResetEvent共同的祖先,他們包裝了用于同步的內(nèi)核對(duì)象,也就是說(shuō)是這些內(nèi)核對(duì)象的托管版本。
Mutex:類似于一個(gè)接力棒,拿到接力棒的線程才可以開始跑,當(dāng)然接力棒一次只屬于一個(gè)線程(Thread Affinity),如果這個(gè)線程不釋放接力棒(Mutex.ReleaseMutex),那么沒辦法,其他所有需要接力棒運(yùn)行的線程都知道能等著看熱鬧
Semaphore:類似于一個(gè)小桶,里面裝了幾個(gè)小球,凡是拿到小球就可以跑,比如指定小桶里最初有四個(gè)小球,那么開始的四個(gè)線程就可以直接拿著自己的小球開跑,但是第五個(gè)線程一看,小球被拿光了,就只好乖乖的等著有誰(shuí)放一個(gè)小球到小桶里(Semophore.Release),他才能跑,但是這里的游戲規(guī)則比較特殊,我們可以隨意向小桶里放入小球,也就是說(shuō)我可以拿走一個(gè)小球,放回去倆,甚至一個(gè)都不拿,放回去5個(gè),這樣就有五個(gè)線程可以拿著這些小球運(yùn)行了.我們可以規(guī)定小桶里有開始有幾個(gè)小球(構(gòu)造函數(shù)的第一個(gè)參數(shù)),也可以規(guī)定最多不能超過(guò)多少小球(構(gòu)造函數(shù)的第二個(gè)參數(shù))
ManualResetEvent,AutoResetEvent可以參考http://www.cnblogs.com/uubox/archive/2007/12/18/1003953.html(內(nèi)核對(duì)象同步,講的很通俗易懂ManuResetEvent,AutoResetEvent
什么是用雙鎖實(shí)現(xiàn)Singleton,為什么要這樣做,雙鎖檢驗(yàn)是不安全的嗎?
使用雙鎖檢驗(yàn)技巧來(lái)實(shí)現(xiàn)單件,來(lái)自于Java社區(qū)
這樣做其實(shí)是為了提高效率,比起
public static MySingleton Instance{
get{
lock(_instance){
if(s_value==null){
_instance= new MySingleton();
}
}
前一種方法在instance創(chuàng)建的時(shí)候不需要用lock同步,從而增進(jìn)了效率
在java中這種技巧被證明是不安全的詳細(xì)見http://www.cs.umd.edu/~pugh/java/memoryModel/
但是在.Net下,這樣的技巧是成立的,因?yàn)?Net使用了改進(jìn)的內(nèi)存模型
并且在.Net下,我們可以使用LazyInit來(lái)實(shí)現(xiàn)單件
private static readonly _instance=new MySingleton()
public static MySingleton Instance{
get{return _instance}
}
當(dāng)?shù)谝淮耸褂胈instance時(shí),CLR會(huì)生成這個(gè)對(duì)象,以后再訪問(wèn)這個(gè)字段,將會(huì)直接返回
互斥對(duì)象(Mutex),信號(hào)量(Semaphore),事件(Event)對(duì)象與lock語(yǔ)句的比較
首先這里所謂的事件對(duì)象不是System.Event,而是一種用于同步的內(nèi)核機(jī)制
互斥對(duì)象和事件對(duì)象屬于內(nèi)核對(duì)象,利用內(nèi)核對(duì)象進(jìn)行線程同步,線程必須要在用戶模式和內(nèi)核模式間切換,所以一般效率很低,但利用互斥對(duì)象和事件對(duì)象這樣的內(nèi)核對(duì)象,可以在多個(gè)進(jìn)程中的各個(gè)線程間進(jìn)行同步。
lock或者M(jìn)oniter是.net用一個(gè)特殊結(jié)構(gòu)實(shí)現(xiàn)的,不涉及模式切換,也就是說(shuō)工作在用戶方式下,同步速度較快,但是不能跨進(jìn)程同步
剛剛接觸鎖定的程序員往往覺得這個(gè)世界非常的危險(xiǎn),每個(gè)靜態(tài)變量似乎都有可能產(chǎn)生競(jìng)爭(zhēng)
首先鎖定是解決競(jìng)爭(zhēng)條件的,也就是多個(gè)線程同時(shí)訪問(wèn)某個(gè)資源,造成意想不到的結(jié)果,比如,最簡(jiǎn)單的情況,一個(gè)計(jì)數(shù)器,如果兩個(gè)線程同時(shí)加一,后果就是損失了一個(gè)計(jì)數(shù),但是頻繁的鎖定又可能帶來(lái)性能上的消耗,還有最可怕的情況,死鎖
到底什么情況下我們需要使用鎖,什么情況下不用呢?
只有共享資源才需要鎖定
首先,只有可以被多線程訪問(wèn)的共享資源才需要考慮鎖定,比如靜態(tài)變量,再比如某些緩存中的值,屬于線程內(nèi)部的變量不需要鎖定
把鎖定交給數(shù)據(jù)庫(kù)
數(shù)據(jù)庫(kù)除了存儲(chǔ)數(shù)據(jù)之外,還有一個(gè)重要的用途就是同步,數(shù)據(jù)庫(kù)本身用了一套復(fù)雜的機(jī)制來(lái)保證數(shù)據(jù)的可靠和一致性,這就為我們節(jié)省了很多的精力.保證了數(shù)據(jù)源頭上的同步,我們多數(shù)的精力就可以集中在緩存等其他一些資源的同步訪問(wèn)上了
了解你的程序是怎么運(yùn)行的
實(shí)際上在web開發(fā)中大多數(shù)邏輯都是在單個(gè)線程中展開的,無(wú)論asp.net還是php,一個(gè)請(qǐng)求都會(huì)在一個(gè)單獨(dú)的線程中處理,其中的大部分變量都是屬于這個(gè)線程的,根本沒有必要考慮鎖定,當(dāng)然對(duì)于asp.net中的application對(duì)象中的數(shù)據(jù),我們就要小心一些了
WinForm中凡是使用BeginInvoke和Invoke調(diào)用的方法也都不需要考慮同步,因?yàn)檫@用這兩個(gè)方法調(diào)用的方法會(huì)在UI線程中執(zhí)行,因此實(shí)際是同步的,所以如果調(diào)用的方法中存在某些靜態(tài)變量,不需要考慮鎖定
業(yè)務(wù)邏輯對(duì)事務(wù)和線程安全的要求
這條是最根本的東西,開發(fā)完全線程安全的程序是件很費(fèi)時(shí)費(fèi)力的事情,在電子商務(wù)等涉及金融系統(tǒng)的案例中,許多邏輯都必須嚴(yán)格的線程安全,所以我們不得不犧牲一些性能,和很多的開發(fā)時(shí)間來(lái)做這方面的工作,而一般的應(yīng)用中,許多情況下雖然程序有競(jìng)爭(zhēng)的危險(xiǎn),我們還是可以不使用鎖定,比如有的時(shí)候計(jì)數(shù)器少一多一,對(duì)結(jié)果無(wú)傷大雅的情況下,我們就可以不用去管他
計(jì)算一下沖突的可能性
我以前曾經(jīng)談到過(guò),架構(gòu)不要過(guò)設(shè)計(jì),其實(shí)在這里也一樣,假如你的全局緩存里的某個(gè)值每天只有幾百或者幾千個(gè)訪問(wèn),并且訪問(wèn)時(shí)間很短,并且分布均勻(實(shí)際上這是大多數(shù)的情況),那么沖突的可能性就非常的少,也許每500天才會(huì)出現(xiàn)一次或者更長(zhǎng),從7*24小時(shí)安全服務(wù)的角度來(lái)看,也完全符合要求,那么你還會(huì)為這樣萬(wàn)分之一的可能性花80%的精力去設(shè)計(jì)嗎?
請(qǐng)多使用lock,少用Mutex
如果你一定要使用鎖定,請(qǐng)盡量不要使用內(nèi)核模塊的鎖定機(jī)制,比如.net的Mutex,Semaphore,AutoResetEvent,ManuResetEvent,使用這樣的機(jī)制涉及到了系統(tǒng)在用戶模式和內(nèi)核模式間的切換,所以性能差很多,但是他們的優(yōu)點(diǎn)是可以跨進(jìn)程同步線程,所以應(yīng)該清楚的了解到他們的不同和適用范圍
應(yīng)用程序池,WebApplication,和線程池之間有什么關(guān)系
一個(gè)應(yīng)用程序池是一個(gè)獨(dú)立的進(jìn)程,擁有一個(gè)線程池,應(yīng)用程序池中可以有多個(gè)WebApplication,每個(gè)運(yùn)行在一個(gè)單獨(dú)的AppDomain中,這些WebApplication公用一個(gè)線程池
不同的AppDomain保證了每個(gè)WebApplication的靜態(tài)變量不會(huì)互相干擾,不同的應(yīng)用程序池保證了一個(gè)網(wǎng)站癱瘓,其他不同進(jìn)程中的站點(diǎn)還能正常運(yùn)行
下圖說(shuō)明了他們的關(guān)系
Web頁(yè)面怎么調(diào)用異步WebService
把Page的Async屬性設(shè)置為true,就可以調(diào)用異步的方法,但是這樣調(diào)用的效果可能并不如我們的相像,請(qǐng)參考Web中使用多線程來(lái)增強(qiáng)用戶體驗(yàn)
推薦文章
http://www.cnblogs.com/uubox/archive/2007/12/18/1003953.html)
http://alang79.blogdriver.com/alang79/456761.html
A low-level Look at the ASP.NET Architecture
其他:
NET多線程同步和互斥機(jī)制初探
http://www.cnblogs.com/herony420/articles/221523.html
使用線程池
http://www.cnblogs.com/three/archive/2006/10/11/526082.html
如何對(duì)制造者線程和使用者線程進(jìn)行同步
http://www.cnblogs.com/three/archive/2006/09/29/518519.html
如何:創(chuàng)建和終止線程
http://www.cnblogs.com/three/archive/2006/09/30/519034.html
HOW TO:使用 Visual C# .NET 同步對(duì)多線程環(huán)境中共享資源的訪問(wèn)
http://support.microsoft.com/kb/816161/zh-cn
聯(lián)系客服