版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
c#線程同步系列(一) lock與Monitor的用法lock(x){DoSomething();}這等效于:try{DoSomething();}finally{}臨界區(qū)&Lock一個機會,索性把線程同步的問題在 C#里面的東西都粗略看了下。第一印象,C#關于線程同步的東西好多,保持了C#一貫的大雜燴和四不象風格(Java/Delphi)。臨界區(qū)跟Java差不多只不過關鍵字用 lock替代了synchronized,然后又用Moniter的Wait/Pulse取代了Object的Wait/Notify,另外又搞出來幾個Event??讓人甚是不明了。不管那么多,一個一個來吧。臨界區(qū)(CriticalSection)是一段在同一時候只被一個線程進入要有這個東西?
/執(zhí)行的代碼。為啥是因為這段代碼訪問了“臨界資源” ,而這種資源只能同時被互斥地訪問。舉個例子來說,你的銀行賬戶就是一個互斥資源,一個銀行系統(tǒng)里面改變余額(存?。┑牟僮鞔a就必須用在臨界區(qū)內。如果你的賬戶余額是 $100,000(如果是真的,那么你就不用再往下看了,還是睡覺去吧) ,假設有兩個人同時給你匯款 $50,000。有兩個線程分別執(zhí)行這兩筆匯款業(yè)務,線程 A在獲取了你的賬戶余額后,在它把新余額($150000)儲存回數(shù)據(jù)庫以前,操作系統(tǒng)把這個線程暫停轉而把CPU的時間片分給另一個線程(是的,這太巧了) ;那么線程B此時取出的賬戶余額仍然是 $10000,隨后線程B幸運的得到的 CPU時間把$50000存入你的賬戶,那么余額變成$150000。而此后某個時候,線程 A再次得以執(zhí)行,它也把“新”余額$150000更新到系統(tǒng)??于是你的$50000就這么憑空消失了。(此段省去常見到一個示例圖,請自行想象)是因為OS的多任務調度,其實在原因一里面已經(jīng)提到。如果OS不支持多任務調度,那么線程A/線程B執(zhí)行更新余額的操作總是一個接一個進行,那么完全不會有上面的問題了。在多線程的世界里,你必須隨時做好你的代碼執(zhí)行過程隨時失去控制的準備;你需要好好考慮當代碼重新執(zhí)行的時候,是否可以繼續(xù)正確的執(zhí)行。一句話,你的程序段在多線程的世界里,你所寫的方法并不是“原子性”的操作。Lock關鍵字C#提供lock關鍵字實現(xiàn)臨界區(qū), MSDN里給出的用法:ObjectthisLock=newObject();lock(thisLock){//Criticalcodesection}lock實現(xiàn)臨界區(qū)是通過“對象鎖”的方式,注意是“對象”,所以你只能鎖定一個引用類型而不能鎖定一個值類型。第一個執(zhí)行該代碼的線程,成功獲取對這個對象的鎖定,進而進入臨界區(qū)執(zhí)行代碼。而其它線程在進入臨界區(qū)前也會請求該鎖,如果此時第一個線程沒有退出臨界區(qū),對該對象的鎖定并沒有解除,那么當前線程會被阻塞, 等待對象被釋放。既然如此,在使用lock時,要注意不同線程是否使用同一個“鎖”作為 lock的對象?,F(xiàn)在回頭來看 MSDN的這段代碼似乎很容易讓人誤解,容易讓人聯(lián)想到這段代碼是在某個方法中存在,以為 thisLock是一個局部變量,而局部變量的生命周期是在這個方法內部,所以當不同線程調用這個方法的時候,他們分別請求了不同的局部變量作為鎖,那么他們都可以分別進入臨界區(qū)執(zhí)行代碼。 因此在MSDN隨后真正的示例中,thisLock實際上是一個 private的類成員變量:usingSystem;usingSystem.Threading;classAccount{privateObjectthisLock=newObject();intbalance;Randomr=newRandom();publicAccount(intinitial){balance=initial;}intWithdraw(intamount){//Thisconditionwillneverbetrueunlessthelockstatementiscommentedout:if(balance<0){thrownewException("NegativeBalance");}Commentoutthenextlinetoseetheeffectofleavingoutthelockkeyword:lock(thisLock){if(balance>=amount){Withdrawal:WithdrawWithdrawal
Console.WriteLine("Balancebefore"+balance);Console.WriteLine("Amountto:-"+amount);balance=balance-amount;Console.WriteLine("Balanceafter: "+balance);returnamount;}else{return0;//transactionrejected}}}publicvoidDoTransactions(){for(inti=0;i<100;i++){Withdraw(r.Next(1,100));}}}classTest{staticvoidMain(){Thread[]threads=newThread[10];Accountacc=newAccount(1000);for(inti=0;i<10;i++){Threadt=newThread(newThreadStart(acc.DoTransactions));threads[i]=t;}for(inti=0;i<10;i++){threads[i].Start();}}}這個例子中,Account對象只有一個,所以臨界區(qū)所請求的“鎖”是唯一的,因此用類的成員變量是可以實現(xiàn)互斥意圖的,其實用大家通常喜歡的 lock(this)也未嘗不可,也即請求這個Account實例本身作為鎖。但是如果在某種情況你的類實例并不唯一或者一個類的幾個方法之間都必須要互斥,那么就要小心了。必須牢記一點,所有因為同一互斥資源而需要互斥的操作,必須請求“同一把鎖”才有效。假設這個 Account類并不只有一個 Withdraw方法修改balance,而是用Withdraw()來特定執(zhí)行取款操作,另有一個Deposit()方法專門執(zhí)行存款操作。很顯然這兩個方法必須是互斥執(zhí)行的,所以這兩個方法中所用到的鎖也必須一致;不能一個用thisLock,另一個重新用一個 privateObjectthisLock1=newObject()。再進一步,其實這個操作場景下各個互斥區(qū)存在的目的是因為有“Balance”這個互斥資源,所有有關Balance的地方應該都是互斥的(如果你不介意讀取操作讀到的是臟數(shù)據(jù)的話,當然也可以不用)。題外話:這么看來其實用Balance本身作為鎖也許更為符合“邏輯”,lock住需要互斥的資源本身不是更好理解么?不過這里Balance是一個值類型,你并不能直接對它lock(你可能需要用到volatile關鍵字,它能在單CPU的情況下確保只有一個線程修改一個變量)。Lock使用的建議關于使用 Lock微軟給出的一些建議。你能夠在 MSDN上找到這么一段話:通常,應避免鎖定 public類型,否則實例將超出代碼的控制范圍。常見的結構 lock(this)、lock(typeof(MyType))和lock("myLock") 違反此準則:1.如果實例可以被公共訪問,將出現(xiàn)
lock(this)
問題。2.如果 MyType可以被公共訪問,將出現(xiàn) lock(typeof(MyType)) 問題。3.由于進程中使用同一字符串的任何其他代碼將共享同一個鎖,所以出現(xiàn) lock("myLock") 問題。4.最佳做法是定義 private對象來鎖定,或privatestatic對象變量來保護所有實例所共有的數(shù)據(jù)。lock(this)的問題我是這么理解:處于某種原因 Account在整個程序空間內不是唯一,那么不同Account實例的相應方法就不可能互斥,因為他們請求的是不同Accout實例內部的不同的鎖。這時候微軟示例中的privateObjectthisLock仍然也避免不了這個問題,而需要使用privatestaticObjectthisLock 來解決問題,因為 static變量是所有類實例共享的。猜想就算Account只有一個實例,但是如果在程序內部被多個處理不同任務的線程訪問,那么 Account實例可能會被某段代碼直接作為鎖鎖定;這相當于你自己鎖定了自己,而別人在不告訴你的情況下也可以能鎖定你。這些情況都是你在寫Account這個類的時候并沒有辦法作出預測的,所以你的Withdraw代碼可能被掛起,在多線程的復雜情況下也容易造成死鎖。不管怎樣,你寫這段代碼的時候肯定不會期待外部的代碼跟你使用了同一把鎖吧?這樣很危險。另外,從面向對象來說,這等于把方法內部的東西隱式的暴露出去。為了實現(xiàn)互斥,專門建立不依賴系 this的代碼機制總是好的;thisLock,專事專用,是個好習慣。MyType的問題跟lock(this)差不多理解,不過比lock(this)更嚴重。因為Lock(typeof(MyType))鎖定住的對象范圍更為廣泛,由于一個類的所有實例都只有一個類對象(就是擁有Static成員的那個對象實例),鎖定它就鎖定了該對象的所有實例。同時lock(typeof(MyType))是個很緩慢的過程,并且類中的其他線程、甚至在同一個應用程序域中運行的其他程序都可以訪問該類型對象,因此,它們都有可能鎖定類對象,完全阻止你代碼的執(zhí)行,導致你自己代碼的掛起或者死鎖。至于lock("myLock"),是因為在.NET中字符串會被暫時存放。如果兩個變量的字符串內容相同的話,.NET會把暫存的字符串對象分配給該變量。所以如果有兩個地方都在使用lock(“mylock”)的話,它們實際鎖住的是同一個對象。.NET集合類對lock的支持在多線程環(huán)境中,常會碰到的互斥資源應該就是一些容器/集合。因此.NET在一些集合類中(比如ArrayList,HashTable,Queue,Stack,包括新增的支持泛型的List)已經(jīng)提供了一個供 lock使用的對象 SyncRoot。在.Net1.1中大多數(shù)集合類的SyncRoot屬性只有一行代碼:returnthis,這樣和lock(集合的當前實例)是一樣的。不過ArrayList中的SyncRoot有所不同(這個并不是我反編譯的,我并沒有驗證這個說法):get{if(this._syncRoot==null){Interlocked.CompareExchange(refthis._syncRoot,newobject(),null);}returnthis._syncRoot;}題外話:上面反編譯的ArrayList的代碼,引出了個Interlocked類,即互鎖操作,用以對某個內存位置執(zhí)行的簡單原子操作。舉例來說在大多數(shù)計算機上,增加變量操作不是一個原子操作,需要執(zhí)行下列步驟:將實例變量中的值加載到寄存器中。增加或減少該值。在實例變量中存儲該值。線程可能會在執(zhí)行完前兩個步驟后被奪走CPU時間,然后由另一個線程執(zhí)行所有三個步驟。當?shù)谝粋€線程重新再開始執(zhí)行時,它改寫實例變量中的值,造成第二個線程執(zhí)行增減操作的結果丟失。這根我們上面提到的銀行賬戶余額的例子是一個道理,不過是更微觀上的體現(xiàn)。我們使用該類提供了的 Increment和Decrement方法就可以避免這個問題。另外,Interlocked類上提供了其它一些能保證對相關變量的操作是原子性的方法。如 Exchange()可以保證指定變量的值交換操作的原子性, Read()保證在32位操作系統(tǒng)中對64位變量的原子讀取。而這里使用的 CompareExchange方法組合了兩個操作:保證了比較和交換操作按原子操作執(zhí)行。此例中 CompareExchange方法將當前 syncRoot和null做比較,如果相等,就用 newobject()替換SyncRoot。在現(xiàn)代處理器中, Interlocked類的方法經(jīng)常可以由單個指令來實現(xiàn),因此它們的執(zhí)行性能非常高。雖然Interlocked沒有直接提供鎖定或者發(fā)送信號的能力,但是你可以用它編寫鎖和信號,從而編寫出高效的非阻止并發(fā)的應用程序。但是這需要復雜的低級別編程能力,因此大多數(shù)情況下使用lock或其它簡單鎖是更好的選擇??吹竭@里是不是已經(jīng)想給微軟一耳光了?一邊教導大家不要用lock(this),一邊竟然在基礎類庫中大量使用??呵呵,我只能說據(jù)傳從.Net2.0開始SyncRoot已經(jīng)是會返回一個單獨的類了,想來大約應該跟ArrayList那種實現(xiàn)差不多,有興趣的可以反編譯驗證下。這里想說,代碼是自己的寫的,最好減少自己代碼對外部環(huán)境的依賴,事實證明即便是.Net基礎庫也不是那么可靠。自己能想到的問題,最好自己寫代碼去處理,需要鎖就自己聲明一個鎖;不再需要一個資源那么自己代碼去 Dispose掉(如果是實現(xiàn)IDisposable接口的)??不要想著什么東西系統(tǒng)已經(jīng)幫你做了。你永遠無法保證你的類將會在什么環(huán)境下被使用,你也無法預見到下一版的 Framework是否偷偷改變了實現(xiàn)。當你代碼莫名其妙不 Work的時候,你是很難找出由這些問題引發(fā)的麻煩。只有你代碼足夠的獨立(這里沒有探討代碼耦合度的問題),才能保證它足夠的健壯;別人代碼的修改(哪怕是你看來“不當”的修改) ,造成你的 Code無法工作不是總有些可笑么 (我還想說“蒼蠅不叮無縫的蛋”“不要因為別人的錯誤連累自己” )?一些集合類中還有一個方法是和同步相關的:Synchronized,該方法返回一個集合的內部類,該類是線程安全的,因為他的大部分方法都用 lock來進行了同步處理(你會不會想那么 SyncRoot顯得多余?別急。)。比如,Add方法會類似于:publicoverridevoidAdd(objectkey,objectvalue){{}}不過即便是這個 Synchronized集合,在對它進行遍歷時,仍然不是一個線程安全的過程。當你遍歷它時,其他線程仍可以修改該它( Add、Remove),可能會導致諸如下標越界之類的異常;就算不出錯,你也可能讀到臟數(shù)據(jù)。若要在遍歷過程中保證線程安全,還必須在整個遍歷過程中鎖定集合,我想這才是SynRoot存在的目的吧:QueuemyCollection=newQueue();lock(myCollection.SyncRoot){foreach(ObjectiteminmyCollection){//Insertyourcodehere.}}提供SynRoot是為了把這個已經(jīng)“線程安全”的集合內部所使用的“鎖”暴露給你,讓你和它內部的操作使用同一把鎖,這樣才能保證在遍歷過程互斥掉其它操作,保證你在遍歷的同時沒有可以修改。另一個可以替代的方法,是使用集合上提供的靜態(tài) ReadOnly()方法,來返回一個只讀的集合,并對它進行遍歷,這個返回的只讀集合是線程安全的。到這里似乎關于集合同步的方法似乎已經(jīng)比較清楚了,不過如果你是一個很迷信 MS基礎類庫的人,那么這次恐怕又會失望了。微軟決定所有從那些自 Framwork3.0以來加入的支持泛型的集合中,如 List,取消掉創(chuàng)建同步包裝器的能力,也就是它們不再有 Synchronized,IsSynchronized也總會返回false;而ReadOnly這個靜態(tài)方法也變?yōu)槊麨锳sReadOnly的實例方法。作為替代,MS建議你仍然使用lock關鍵字來鎖定整個集合。至于
List
之類的泛型集合
SyncRoot
是怎樣實現(xiàn)的,MSDN
是這樣描述的“在
List<(Of<(T>)>)
的默認實現(xiàn)中,此屬性始終返回當前實例。 ”,趕緊去吐血吧!自己的SyncRoot還是上面提過的老話,靠自己,以不變應萬變:publicclassMySynchronizedList{privatereadonlyobjectsyncRoot=newobject();privatereadonlyList<intlist=newList<int>();publicobjectSyncRoot{get{returnthis.syncRoot;}}publicvoidAdd(inti){lock(syncRoot){list.Add(i);}}//...}自已寫一個類,用自己的syncRoot封裝一個線程安全的容器。臨界區(qū)&Monitor監(jiān)視器(Monitor)的概念可以在MSDNpx)上找到下面一段話:與lock關鍵字類似,監(jiān)視器防止多個線程同時執(zhí)行代碼塊。Enter方法允許一個且僅一個線程繼續(xù)執(zhí)行后面的語句; 其他所有線程都將被阻止,直到執(zhí)行語句的線程調用 Exit。這與使用lock關鍵字一樣。事實上, lock關鍵字就是用 Monitor類來實現(xiàn)的。例如:lock(x){DoSomething();}這等效于:System.Objectobj=(System.Object)x;try{DoSomething();}finally{}使用lock關鍵字通常比直接使用Monitor類更可取,一方面是因為lock更簡潔,另一方面是因為lock確保了即使受保護的代碼引發(fā)異常,也可以釋放基礎監(jiān)視器。這是通過finally 關鍵字來實現(xiàn)的,無論是否引發(fā)異常它都執(zhí)行關聯(lián)的代碼塊。這里微軟已經(jīng)說得很清楚了,Lock就是用Monitor實現(xiàn)的,兩者都是C#中對臨界區(qū)功能的實現(xiàn)。用ILDASM打開含有以下代碼的exe或者dll也可以證實這一點(我并沒有自己證實):lock(lockobject){inti=5;}反編譯后的的
IL
代碼為:IL_0045: call voidIL_004a:nop.try{IL_004b:nopIL_004c:ldc.i4.5IL_004d:stloc.1IL_004e:nopIL_004f:leave.sIL_0059}//end.tryfinally{IL_0051:IL_0052:
ldloc.3call
voidIL_0057: nopIL_0058: endfinally}//endhandlerMonitor中和lock等效的方法Monitor是一個靜態(tài)類,因此不能被實例化,只能直接調用Monitor上的各種方法來完成與 lock相同的功能:Enter(object)/TryEnter(object)/TryEnter(object,int32)/TryEnter(object,timespan):用來獲取對象鎖( Lock中已經(jīng)提到過,這里再強調一次,是對象類型而不能是值類型),標記臨界區(qū)的開始。與 Enter不同,TryEnter永遠不會阻塞代碼,當無法獲取對象鎖時它會返回 False,并且調用者不進入臨界區(qū)。 TryEnter還有兩種重載,可以定義一個時間段,在該時間段內一直嘗試獲得對象鎖,超時則返回 False。Exit(object):沒啥好說的,釋放對象鎖、退出臨界區(qū)。只是一定記得在 try的finally塊里調用,否則一但由于異常造成Exit無法執(zhí)行,對象鎖得不到釋放,就會造成死鎖。此外,調用Exit的線程必須擁有 object參數(shù)上的鎖,否則會引發(fā)SynchronizationLockException 異常。在調用線程獲取指定對象上的鎖后,可以重復對該對象進行了相同次數(shù)的
Exit
和Enter
調用;如果調用
Exit
與調用
Enter
的次數(shù)不匹配,那么該鎖不會被正確釋放。上篇中提到的有關lock的所有使用方法和建議,都適用于它們。比lock更“高級”的Monitor到此為止,所有見到的還是我們在 lock中熟悉的東西,再看Monitor的其它方法之前,我們來看看那老掉牙的“生產(chǎn)者和消費者”場景。試想消費者和生產(chǎn)者是兩個獨立的線程,同時訪問一個容器:很顯然這個容器是一個臨界資源(你不會問我為什么是顯然吧?),同時只允許一個線程訪問。生產(chǎn)者往容器里存放生產(chǎn)好的資源;消費者消費掉容器里的資源。粗看這個場景并沒有什么特殊的問題,只要在兩個線程中分別調用兩個方法,這兩個方法內部都用同一把鎖進入臨界區(qū)訪問容器即可??墒菃栴}在于:消費者鎖定容器,進入臨界區(qū)后可能發(fā)現(xiàn)容器是空的。它可以退出臨界區(qū),然后下次再盲目地進入碰碰運氣;如果不退出,那么讓生產(chǎn)者永遠無法進入臨界區(qū),往容器里放入資源供消費者消費,從而造成死鎖。而生產(chǎn)者也可能進入臨界區(qū)后,卻發(fā)現(xiàn)容器是滿的。結果一樣,直接退出等下次來碰運氣;或者不退出造成死鎖。兩者選擇直接退出不會引發(fā)什么問題,無非就是可能多次無功而返。這么做,你的程序邏輯總是有機會得到正確執(zhí)行的,但是效率很低,因為這樣的機制本身是不可控的,業(yè)務邏輯是否得以成功執(zhí)行完全是隨機的。所以我們需要更有效、更“優(yōu)雅”的方式:消費者在進入臨界區(qū)發(fā)現(xiàn)容器為空后,立即釋放鎖并把自己阻塞,等待生產(chǎn)者通知,不再做無謂的嘗試;如果順利消費資源完畢后,主動通知生產(chǎn)者可以進行生產(chǎn)了,隨后仍然阻塞自己等待生產(chǎn)者通知。生產(chǎn)者如果發(fā)現(xiàn)容器是滿的,那么立即釋放鎖并阻塞自己,等待消費者在消費完成后喚醒;在生產(chǎn)完畢后,主動給消費者發(fā)出通知,隨后也仍然阻塞自己,等待消費者告訴自己容器已經(jīng)空了。在按這個思路寫出SampleCode前,我們來看Monitor上需要用的其它重要方法:Wait(Object)/Wait(Object,Int32)/Wait(Object,TimeSpan)/Wait(Object,Int32,Boolean)/Wait(Object,TimeSpan,Boolean): 釋放對象上的鎖并阻塞當前線程,直到它重新獲取該鎖。這里的阻塞是指當前線程進入“ WaitSleepJoin”狀態(tài),此時CPU不再會分配給這種狀態(tài)的線程 CPU時間片,這其實跟在線程上調用 Sleep()時的狀態(tài)一樣。這時,線程不會參與對該鎖的分配爭奪。要打破這種狀態(tài),需要其它擁有該對象鎖的線程,調用下面要講到的Pulse()來喚醒。不過這與, Sleep()不同,只有那些因為該對象鎖阻塞的線程才會被喚醒。此時,線程重新進入“Running”狀態(tài),參與對對象鎖的爭奪。強調一下,Wait()其實起到了 Exit()的作用,也就是釋放當前所獲得的對象鎖。只不過 Wait()同時又阻塞了自己。我們還看到
Wait()的幾個重載方法。其中第
2、3
個方法給Wait
加上了一個時間,如果超時
Wait
會返回不再阻塞,并且可以根據(jù) Wait方法的返回值,以確定它是否已在超時前重新獲取鎖。在這種情況下,其實線程并不需要等待其它線程Pulse()喚醒,相當于Sleep一定時間后醒來。第4、5個方法在第2、3個方法的基礎上加上 exitContent參數(shù),我們暫時不去管它,你可以詳細參見這里: 。Pulse(object):向阻塞線程隊列(由于該 object而轉入WaitSleepJoin狀態(tài)的所有線程,也就是那些執(zhí)行了Wait(object)的線程,存放的隊列)中第一個線程發(fā)信號,該信號通知鎖定對象的狀態(tài)已更改,并且鎖的所有者準備釋放該鎖。收到信號的阻塞線程進入就緒隊列中(那些處于Running狀態(tài)的線程,可以被CPU調用運行的線程在這個隊列里),以便它有機會接收對象鎖。注意,接受到信號的線程只會從阻塞中被喚醒,并不一定會獲得對象鎖。PulseAll(object):與Pulse()不同,阻塞隊列中的所有線程都會收到信號,并被喚醒轉入 Running狀態(tài),即進入就緒隊列中。至于它們誰會幸運的獲得對象鎖,那就要看 CPU了。注意:以上所有方法都只能在臨界區(qū)內被調用,換句話說,只有對象鎖的獲得者能夠正確調用它們,否則會引發(fā)SynchronizationLockException 異常。好了,有了它們我們就可以完成這樣的代碼:usingSystem;usingSystem.Threading;usingSystem.Collections;usingSystem.Linq;usingSystem.Text;classMonitorSample{//容器,一個只能容納一塊糖的糖盒子。 PS:現(xiàn)在MS已經(jīng)不推薦使用 ArrayList,//支持泛型的 List才是應該在程序中使用的,我這里偷懶,不想再去寫一個 Candy類了。privateArrayList_candyBox=newArrayList(1);privatevolatilebool_shouldStop=false;//用于控制線程正常結束的標志<summary>用于結束Produce()和Consume()在輔助線程中的執(zhí)行</summary>publicvoidStopThread(){_shouldStop=true;//這時候生產(chǎn)者/消費者之一可能因為在阻塞中而沒有機會看到結束標志,//而另一個線程順利結束,所以剩下的那個一定長眠不醒,需要我們在這里嘗試叫醒它們。//不過這并不能確保線程能順利結束,因為可能我們剛剛發(fā)送信號以后,線程才阻塞自己。Monitor.Enter(_candyBox);try{Monitor.PulseAll(_candyBox);}finally{Monitor.Exit(_candyBox);}}<summary>生產(chǎn)者的方法</summary>publicvoidProduce(){while(!_shouldStop){Monitor.Enter(_candyBox);try{if(_candyBox.Count==0){_candyBox.Add("Acandy");Console.WriteLine("生產(chǎn)者:有糖吃啦!");//喚醒可能現(xiàn)在正在阻塞中的消費者Monitor.Pulse(_candyBox);Console.WriteLine("生產(chǎn)者:趕快來吃??!");//調用Wait方法釋放對象上的鎖,并使生產(chǎn)者線程狀態(tài)轉為 WaitSleepJoin,阻止該線程被 CPU調用(跟Sleep一樣)//直到消費者線程調用Pulse(_candyBox)使該線程進入到 Running狀態(tài)Monitor.Wait(_candyBox);}else//容器是滿的{Console.WriteLine("生產(chǎn)者:糖罐是滿的!");//喚醒可能現(xiàn)在正在阻塞中的消費者Monitor.Pulse(_candyBox);//調用Wait方法釋放對象上的鎖,并使生產(chǎn)者線程狀態(tài)轉為 WaitSleepJoin,阻止該線程被 CPU調用(跟Sleep一樣)//直到消費者線程調用Pulse(_candyBox)使生產(chǎn)者線程重新進入到 Running狀態(tài),此才語句返回Monitor.Wait(_candyBox);}}finally{Monitor.Exit(_candyBox);}Thread.Sleep(2000);}Console.WriteLine("生產(chǎn)者:下班啦! ");}<summary>消費者的方法</summary>publicvoidConsume(){//即便看到結束標致也應該把容器中的所有資源處理完畢再退出,否則容器中的資源可能就此丟失//不過這里_candyBox.Count是有可能讀到臟數(shù)據(jù)的,好在我們這個例子中只有兩個線程所以問題并不突出//正式環(huán)境中,應該用更好的辦法解決這個問題。while(!_shouldStop||_candyBox.Count>0){Monitor.Enter(_candyBox);try{if(_candyBox.Count==1){_candyBox.RemoveAt(0);if(!_shouldStop){Console.WriteLine("消費者:糖已吃完!");}else{Console.WriteLine("消費者:還有糖沒吃,馬上就完! ");}//喚醒可能現(xiàn)在正在阻塞中的生產(chǎn)者Monitor.Pulse(_candyBox);Console.WriteLine("消費者:趕快生產(chǎn)??!");Monitor.Wait(_candyBox);}else{Console.WriteLine("消費者:糖罐是空的!");//喚醒可能現(xiàn)在正在阻塞中的生產(chǎn)者Monitor.Pulse(_candyBox);Monitor.Wait(_candyBox);}}finally{Monitor.Exit(_candyBox);}Thread.Sleep(2000);}Console.WriteLine("消費者:都吃光啦,下次再吃!");}staticvoidMain(string[]args){MonitorSampless=newMonitorSample();ThreadthdProduce=newThread(newThreadStart(ss.Produce));ThreadthdConsume=newThread(newThreadStart(ss.Consume));//Startthreads.Console.WriteLine("開始啟動線程,輸入回車終止生產(chǎn)者和消費者的工作?? /r/n******************************************");thdProduce.Start();Thread.Sleep(2000); //盡量確保生產(chǎn)者先執(zhí)行thdConsume.Start();Console.ReadLine(); //通過IO阻塞主線程,等待輔助線程演示直到收到一個回車ss.StopThread(); //正常且優(yōu)雅的結束生產(chǎn)者和消費者線程Thread.Sleep(1000); //等待線程結束while(thdProduce.ThreadState!=ThreadState.Stopped){ss.StopThread(); //線程還沒有結束有可能是因為它本身是阻塞的,嘗試使用 StopThread()方法中的PulseAll()喚醒它,讓他看到結束標志thdProduce.Join(1000); //等待生產(chǎn)這線程結束}while(thdConsume.ThreadState!=ThreadState.Stopped){ss.StopThread();thdConsume.Join(1000); //等待消費者線程結束}Console.WriteLine("******************************************/r/n 輸入回車結束! ");Console.ReadLine();}}可能的幾種輸出(不是全部可能) :開始啟動線程,輸入回車終止生產(chǎn)者和消費者的工作??******************************************生產(chǎn)者:有糖吃啦!生產(chǎn)者:趕快來吃?。∠M者:還有糖沒吃,馬上就完!消費者:趕快生產(chǎn)??!生產(chǎn)者:下班啦!消費者:都吃光啦,下次再吃!******************************************輸入回車結束!開始啟動線程,輸入回車終止生產(chǎn)者和消費者的工作??******************************************生產(chǎn)者:有糖吃啦!生產(chǎn)者:趕快來吃??!消費者:糖已吃完!消費者:趕快生產(chǎn)??!生產(chǎn)者:下班啦!消費者:都吃光啦,下次再吃!******************************************輸入回車結束!開始啟動線程,輸入回車終止生產(chǎn)者和消費者的工作??******************************************生產(chǎn)者:有糖吃啦!生產(chǎn)者:趕快來吃!!消費者:糖已吃完!消費者:趕快生產(chǎn)??!生產(chǎn)者:有糖吃啦!生產(chǎn)者:趕快來吃!!消費者:還有糖沒吃,馬上就完!消費者:趕快生產(chǎn)!!生產(chǎn)者:下班啦!消費者:都吃光啦,下次再吃!******************************************輸入回車結束!有興趣的話你還可以嘗試修改生產(chǎn)者和消費者的啟動順序,嘗試下其它的結果(比如糖罐為空)。其實生產(chǎn)者和消費者方法中那個Sleep(2000)也是為了方便手工嘗試出不同分支的執(zhí)行情況,輸出中的空行就是我敲入回車讓線程中止的時機。你可能已經(jīng)發(fā)現(xiàn),除非消費者先于生產(chǎn)者啟動,否則我們永遠不會看到消費者說“糖罐是空的!”,這是因為消費者在吃糖以后把自己阻塞了,直到生產(chǎn)者生產(chǎn)出糖塊后喚醒自己。另一方面,生產(chǎn)者即便先于消費者啟動,在這個例子中我們也永遠不會看到生產(chǎn)者說“糖罐是滿的!”,因為初始糖罐為空且生產(chǎn)者在生產(chǎn)后就把自己阻塞了。題外話1:是不是覺得生產(chǎn)者判斷糖罐是滿的、消費者檢查出糖罐是
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 鑄鐵閘門放水試驗施工方案
- 燃料助劑項目可行性研究報告
- 老年公寓裝修設計合同模板
- 學校翻新項目解除合同書
- 專賣店翻新合同取消
- 湖北孝感美珈職業(yè)學院《酒店新媒體運營》2023-2024學年第一學期期末試卷
- 2025年度貨物運輸司機合作合同3篇
- 2025年度銷售代理合同糾紛訴訟狀3篇
- 湖北2025年湖北城市建設職業(yè)技術學院招聘5人筆試歷年參考題庫附帶答案詳解
- 2025年房屋買賣合同含車庫6篇
- 2024年日語培訓機構市場供需現(xiàn)狀及投資戰(zhàn)略研究報告
- 2024年公安機關理論考試題庫附參考答案(基礎題)
- 歷史-廣東省大灣區(qū)2025屆高三第一次模擬試卷和答案
- 2024年安全生產(chǎn)法律、法規(guī)、標準及其他要求清單
- 2023年高考文言文閱讀設題特點及備考策略
- 抗心律失常藥物臨床應用中國專家共識
- 考級代理合同范文大全
- 2024解析:第三章物態(tài)變化-講核心(原卷版)
- DB32T 1590-2010 鋼管塑料大棚(單體)通 用技術要求
- 安全行車知識培訓
- 2024年安徽省高校分類對口招生考試數(shù)學試卷真題
評論
0/150
提交評論