


版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、CTGS-資料文件注:本文中出現(xiàn)的代碼均在 .net Framework RC3 環(huán)境中運(yùn)行通過(guò) 多線程的概念1操縱一個(gè)線程2 線程的同步和通訊 一一生產(chǎn)者和消費(fèi)者6 線程池和定時(shí)器 一一多線程的自動(dòng)管理13 互斥對(duì)象 更加靈活的同步方式19小結(jié)22多線程的概念Windows是一個(gè)多任務(wù)的系統(tǒng),如果你使用的是windows2000及其以上版本,你可以通過(guò)任務(wù)管理器查看當(dāng)前系統(tǒng)運(yùn)行的程序和進(jìn)程。什么是進(jìn)程呢?當(dāng)一個(gè)程序開始運(yùn)行時(shí),它就是一個(gè)進(jìn)程,進(jìn)程所指包括運(yùn)行中的 程序和程序所使用到的內(nèi)存和系統(tǒng)資源。而一個(gè)進(jìn)程又是由多個(gè)線程所組成的,線程是程序中的一個(gè)執(zhí)行流,每 個(gè)線程都有自己的專有寄存器 (
2、棧指針、程序計(jì)數(shù)器等),但代碼區(qū)是共享的,即不同的線程可以執(zhí)行同樣的函 數(shù)。多線程是指程序中包含多個(gè)執(zhí)行流,即在一個(gè)程序中可以同時(shí)運(yùn)行多個(gè)不同的線程來(lái)執(zhí)行不同的任務(wù),也就 是說(shuō)允許單個(gè)程序創(chuàng)建多個(gè)并行執(zhí)行的線程來(lái)完成各自的任務(wù)。瀏覽器就是一個(gè)很好的多線程的例子,在瀏覽器中你可以在下載JAVA小應(yīng)用程序或圖象的同時(shí)滾動(dòng)頁(yè)面,在訪問(wèn)新頁(yè)面時(shí),播放動(dòng)畫和聲音,打印文件等。多線程的好處在于可以提高CPU的利用率一一任何一個(gè)程序員都不希望自己的程序很多時(shí)候沒(méi)事可干,在多線程程序中,一個(gè)線程必須等待的時(shí)候,CPU可以運(yùn)行其它的線程而不是等待,這樣就大大提高了程序的效率。然而我們也必須認(rèn)識(shí)到線程本身可能影響
3、系統(tǒng)性能的不利方面,以正確使用線程:線程也是程序,所以線程需要占用內(nèi)存,線程越多占用內(nèi)存也越多多線程需要協(xié)調(diào)和管理,所以需要CPU進(jìn)度跟蹤線程線程之間對(duì)共享資源的訪問(wèn)會(huì)相互影響,必須解決競(jìng)用共享資源的問(wèn)題線程太多會(huì)導(dǎo)致控制太復(fù)雜,最終可能造成很多Bug基于以上認(rèn)識(shí),我們可以一個(gè)比喻來(lái)加深理解。假設(shè)有一個(gè)公司,公司里有很多各司其職的職員,那么我們 可以認(rèn)為這個(gè)正常運(yùn)作的公司就是一個(gè)進(jìn)程,而公司里的職員就是線程。一個(gè)公司至少得有一個(gè)職員吧,同理, 一個(gè)進(jìn)程至少包含一個(gè)線程。在公司里,你可以一個(gè)職員干所有的事,但是效率很顯然是高不起來(lái)的,一我的公 司也不可能做大。一個(gè)程序中也可以只用一個(gè)線程去做事,
4、事實(shí)上,一些過(guò)時(shí)的語(yǔ)言如fortune,basic都是如此,但是象一我的公司一樣,效率很低,如果做大程序,效率更低一一事實(shí)上現(xiàn)在幾乎沒(méi)有單線程的商業(yè)軟件。公司的職員越多,老板就得發(fā)越多的薪水給他們,還得耗費(fèi)大量精力去管理他們,協(xié)調(diào)他們之間的矛盾和利益。 程序也是如此,線程越多耗費(fèi)的資源也越多,需要CPU進(jìn)度去跟蹤線程,還得解決諸如死鎖,同步等問(wèn)題。總之,如果你不想你的公司被稱為皮包公司”,你就得多幾個(gè)員工。如果你不想讓你的程序顯得稚氣,就在你的程序里引入多線程吧!本文將對(duì)C#編程中的多線程機(jī)制進(jìn)行探討,通過(guò)一些實(shí)例解決對(duì)線程的控制,多線程間通訊等問(wèn)題。為了省去創(chuàng)建GUI那些繁瑣的步驟,更清晰地
5、逼近線程的本質(zhì),下面所有的程序都是控制臺(tái)程序,程序最后的 Console.ReadLine()是為了使程序中途停下來(lái),以便看清楚執(zhí)行過(guò)程中的輸出。好了,廢話少說(shuō),讓我們來(lái)體驗(yàn)一下多線程的C#吧!.CTGS-資料文件操縱一個(gè)線程 任何程序在執(zhí)行時(shí),至少有一個(gè)主線程,下面這段小程序可以給讀者一個(gè)直觀的印 象:/SystemThread.csusing System;using System.Threadi ng;n amespace ThreadTestclass Run ItSTAThreadstatic void Main(string args)Thread.Curre ntThread.N
6、ame="System Thread"/給當(dāng)前線程起名為 "System Thread"Co nsole.WriteLi ne(Thread.Curre ntThread.Name+"'Status:"+Thread.Curre ntThread.ThreadState);Con sole.ReadL in e();編譯執(zhí)行后你看到了什么?是的,程序?qū)a(chǎn)生如下輸出:System Thread's Status:R unning在這里,我們通過(guò)Thread類的靜態(tài)屬性 CurrentThread獲取了當(dāng)前執(zhí)行的線程,對(duì)其
7、Name屬性賦值 System Thread ”,最后還輸出了它的當(dāng)前狀態(tài)( ThreadState )。所謂靜態(tài)屬性,就是這個(gè)類所有對(duì)象所公有的屬 性,不管你創(chuàng)建了多少個(gè)這個(gè)類的實(shí)例,但是類的靜態(tài)屬性在內(nèi)存中只有一個(gè)。很容易理解Curre ntThread為什么是靜態(tài)的一一雖然有多個(gè)線程同時(shí)存在,但是在某一個(gè)時(shí)刻,CPU只能執(zhí)行其中一個(gè)。就像上面程序所演示的,我們通過(guò)Thread類來(lái)創(chuàng)建和控制線程。注意到程序的頭部,我們使用了如下命名空間:.usin gSystem;usin gSystem.Thread ing;在.n et framework class library中,所有與多線程機(jī)
8、制應(yīng)用相關(guān)的類都是放在System.Threadi ng 命名空間中的。其中提供Thread類用于創(chuàng)建線程,ThreadPool類用于管理線程池等等,此外還提供解決了線程執(zhí)行安排, 死鎖,線程間通訊等實(shí)際問(wèn)題的機(jī)制。如果你想在你的應(yīng)用程序中使用多線程,就必須包含這個(gè)類。Thread類有幾個(gè)至關(guān)重要的方法,描述如下:. Start():啟動(dòng)線程 Sleep(int):靜態(tài)方法,暫停當(dāng)前線程指定的毫秒數(shù) Abort():通常使用該方法來(lái)終止一個(gè)線程 Suspend():該方法并不終止未完成的線程,它僅僅掛起線程,以后還可恢復(fù)。 Resume():恢復(fù)被Suspend()方法掛起的線程的執(zhí)行CTGS
9、-資料文件下面我們就動(dòng)手來(lái)創(chuàng)建一個(gè)線程,使用Thread類創(chuàng)建線程時(shí),只需提供線程入口即可。線程入口使程序知道該讓這個(gè)線程干什么事,在C#中,線程入口是通過(guò)ThreadStart代理(delegate )來(lái)提供的,你可以把ThreadStart理解為一個(gè)函數(shù)指針,指向線程要執(zhí)行的函數(shù),當(dāng)調(diào)用 Thread.Start()方法后,線程就開始執(zhí)行 ThreadStart 所代表或者說(shuō)指向的函數(shù)。打開你的VS.net ,新建一個(gè)控制臺(tái)應(yīng)用程序(ConsoleApplication ),下面這些代碼將讓你體味到完全控制一個(gè)線程的無(wú)窮樂(lè)趣!/ThreadTest.cs usin gSystem;usin
10、 gSystem.Threadi ng;n amespaceThreadTestpublicclassAlphapublicvoidBeta()while(true)C on sole.WriteLi ne("Alpha.Betaisru nningin itsow nthread.");publicclassSi mplepublicstatici ntMai n( )Co nsole.WriteLi ne("ThreadStart/Stop/Joi nSample");AlphaoAlpha=n ewAlpha();file:/ 這里創(chuàng)建一個(gè)線程,使
11、之執(zhí)行Alpha 類的 Beta()方法 ThreadoThread=newThread(newThreadStart(oAlpha.Beta);oThread.Start();while(!oThread.lsAlive);Thread.Sleep(1);oThread.Abort();oThread .J oi n( );Co nsole.WriteLi ne();Co nsole.WriteLi ne("Alpha.Betahasfi nished");tryCo nsole.WriteLi ne("TrytorestarttheAlpha.Betathre
12、ad"); oThread.Start();catch(ThreadStateExceptio n)Con sole.Write("ThreadStateExcepti ontryin gtorestartAlpha.Beta.");C on sole.WriteL in e("Expecteds in ceabortedthreadsca nno tberestarted.");C on sole.ReadL in e();retur nO;這段程序包含兩個(gè)類Alpha和Simple,在創(chuàng)建線程oThread時(shí)我們用指向 Alpha.Beta
13、()方法的初始化了 ThreadStart代理(delegate )對(duì)象,當(dāng)我們創(chuàng)建的線程oThread調(diào)用oThread.Start()方法啟動(dòng)時(shí),實(shí)際上程序運(yùn)行的是 Alpha.Beta()方法:.AlphaoAlpha=n ewAlpha();ThreadoThread=n ewThread( newThreadStart(oAlpha.Beta);oThread.Start();然后在Main()函數(shù)的while循環(huán)中,我們使用靜態(tài)方法Thread.Sleep()讓主線程停了 1ms,這段進(jìn)度 CPU轉(zhuǎn)向執(zhí)行線程oThread。然后我們?cè)噲D用 Thread.Abort()方法終止線程o
14、Thread,注意后面的oThread.Join() ,Thr ead.Join()方法使主線程等待,直到oThread線程結(jié)束。你可以給Thread.Join()方法指定一個(gè)int型的參數(shù)作為等待的最長(zhǎng)進(jìn)度。之后,我們?cè)噲D用Thread.Start()方法重新啟動(dòng)線程 oThread,但是顯然Abort()方法帶來(lái)的后果是不可恢復(fù)的終止線程,所以最后程序會(huì)拋出ThreadStateExcepti on異常。程序最后得到的結(jié)果將如下F: Wisual Studio Proj ectsConsoleAppl讓at專'Thr亡玄(0話土. ezeAlpha.Betaisrunnimgini
15、tsnunthread_Hlpha.Betaispum nlnginitsownthreadAlpha.Betaisrunninginitsoirnthread.Alpha.Betaisrunnimginitsownthread-Hlpha.BetaismirminginitsownthreadAlpha.Betaisrunninginitsomnthread-Alpha.Betaisrunnimginitsownthread.isrunninginitsounthreadHlpha.Betaisrunninginitsovunthread.Alpha.BetaisrunninginitsDu
16、nthread.nipha.BetaisrunninginitsownthreadHlpha.Betaisrunn inginitsovunthread.Alpha.Betaisrunninginitsownthread.Alpha.BetaisFunninginitsownthreadHlpha.Betaisrunninginitsowinthread.Alpha.Betais;runnimginitsownthread.Alpha.BetaisFunninginitsownthread.Hlpha.Betaisrunninginitsouinthread.Alpha.Betaisrunni
17、niginitsownthread.Jiast finlshndFry to restart the Alpha.Beta threadThreadStateException trying Co restart AlphaExpected since aborted thread s cannot be pestarted.圖:在這里我們要注意的是其它線程都是依附于Main()函數(shù)所在的線程的,Main()函數(shù)是C#程序的入口,起始線程可以稱之為主線程,如果所有的前臺(tái)線程都停止了,那么主線程可以終止,而所有的后臺(tái)線程都將無(wú)條件終止。而所有的線程雖然在微觀上是串行執(zhí)行的,但是在宏觀上你完全可以
18、認(rèn)為它們?cè)诓⑿袌?zhí)行。CTGS-資料文件讀者一定注意到了Thread.ThreadState 這個(gè)屬性,這個(gè)屬性代表了線程運(yùn)行時(shí)狀態(tài),在不同的情況下有不同的值,于是我們有時(shí)候可以通過(guò)對(duì)該值的判斷來(lái)設(shè)計(jì)程序進(jìn)程安排。ThreadState在各種情況下的可能取值如下:.« Aborted :線程已停止* AbortRequested :線程的Thread.Abort()方法已被調(diào)用,但是線程還未停止« Background :線程在后臺(tái)執(zhí)行,與屬性 Thread.lsBackground 有關(guān)« Running :線程正在正常運(yùn)行« Stopped :線程已經(jīng)
19、被停止« StopRequested :線程正在被要求停止* Suspended :線程已經(jīng)被掛起(此狀態(tài)下,可以通過(guò)調(diào)用Resume()方法重新運(yùn)行)« SuspendRequested :線程正在要求被掛起,但是未來(lái)得及響應(yīng)« Un started :未調(diào)用Thread.Start()開始線程的運(yùn)行« WaitSleepJoin :線程因?yàn)檎{(diào)用了Wait(),Sleep()或Join()等方法處于封鎖狀態(tài)上面提到了 Background狀態(tài)表示該線程在后臺(tái)運(yùn)行,那么后臺(tái)運(yùn)行的線程有什么特別的地方呢?其實(shí)后臺(tái)線程跟前臺(tái)線程只有一個(gè)區(qū)別,那就是后臺(tái)線程不
20、妨礙程序的終止。一旦一個(gè)進(jìn)程所有的前臺(tái)線程都終止后,CLR (通用語(yǔ)言運(yùn)行環(huán)境)將通過(guò)調(diào)用任意一個(gè)存活中的后臺(tái)進(jìn)程的Abort()方法來(lái)徹底終止進(jìn)程。當(dāng)線程之間爭(zhēng)奪CPU進(jìn)度時(shí),CPU按照是線程的優(yōu)先級(jí)給予服務(wù)的。在C#應(yīng)用程序中,用戶可以設(shè)定 5個(gè)不同的優(yōu)先級(jí),由高到低分別是 Highest,AboveNormal , Normal, BelowNormal ,Lowest,在創(chuàng)建線程時(shí)如果不指定優(yōu)先級(jí), 那么系統(tǒng)默認(rèn)為 ThreadPriority.Normal 。給一個(gè)線程指定優(yōu)先級(jí),我們可以使用如下代碼:/ 設(shè)定優(yōu)先級(jí)為最低myThread.Priority=ThreadPriorit
21、 y. Lowest;通過(guò)設(shè)定線程的優(yōu)先級(jí),我們可以安排一些相對(duì)重要的線程優(yōu)先執(zhí)行,例如對(duì)用戶的響應(yīng)等等?,F(xiàn)在我們對(duì)怎樣創(chuàng)建和控制一個(gè)線程已經(jīng)有了一個(gè)初步的了解,下面我們將深入研究線程實(shí)現(xiàn)中比較典型的 的問(wèn)題,并且探討其解決方法。.線程的同步和通訊 一一生產(chǎn)者和消費(fèi)者假設(shè)這樣一種情況,兩個(gè)線程同時(shí)維護(hù)一個(gè)隊(duì)列,如果一個(gè)線程對(duì)隊(duì)列中添加元素,而另外一個(gè)線程從隊(duì)列中 取用元素,那么我們稱添加元素的線程為生產(chǎn)者,稱取用元素的線程為消費(fèi)者。生產(chǎn)者與消費(fèi)者問(wèn)題看起來(lái)很簡(jiǎn) 單,但是卻是多線程應(yīng)用中一個(gè)必須解決的問(wèn)題,它涉及到線程之間的同步和通訊問(wèn)題。前面說(shuō)過(guò),每個(gè)線程都有自己的資源,但是代碼區(qū)是共享的,即
22、每個(gè)線程都可以執(zhí)行相同的函數(shù)。但是多線程環(huán)境下,可能帶來(lái)的 問(wèn)題就是幾個(gè)線程同時(shí)執(zhí)行一個(gè)函數(shù),導(dǎo)致數(shù)據(jù)的混亂,產(chǎn)生不可預(yù)料的結(jié)果,因此我們必須避免這種情況的發(fā) 生。C#提供了一個(gè)關(guān)鍵字lock,它可以把一段代碼定義為互斥段( criticalsection ),互斥段在一個(gè)時(shí)刻內(nèi)只允 許一個(gè)線程進(jìn)入執(zhí)行,而其他線程必須等待。在C#中,關(guān)鍵字lock定義如下:.lock(expressi on) stateme nt_blockexpression代表你希望跟蹤的對(duì)象,通常是對(duì)象引用。一般地,如果你想保護(hù)一個(gè)類的實(shí)例,你可以使用this。如果你希望保護(hù)一個(gè)靜態(tài)變量(如互斥代碼段在一個(gè)靜態(tài)方法內(nèi)部
23、),一般使用類名就可以了。而stateme nt_blCTGS-資料文件ock就是互斥段的代碼,這段代碼在一個(gè)時(shí)刻內(nèi)只可能被一個(gè)線程執(zhí)行。下面是一個(gè)使用lock關(guān)鍵字的典型例子,我將在注釋里向大家說(shuō)明lock關(guān)鍵字的用法和用途:/lock.csusin gSystem;usin gSystem.Threadi ng;in ternalclassAcco untin tbala nce;Ra ndomr=n ewRa ndom();i ntern alAcco un t(i ntin itial)bala nce=i nitial;i nternali ntWithdraw(i ntamou n
24、t)if(bala nee<0) file:/ 如果 balanee 小于 0 則拋出異常 thrownewException("NegativeBalanee");/ 下面的代碼保證在當(dāng)前線程修改balanee的值完成之前/不會(huì)有其他線程也執(zhí)行這段代碼來(lái)修改balanee的值 /因此,balanee 的值是不可能小于0 的 lock(this)Console.WriteLine("CurrentThread:"+Thread.CurrentThread.Name);file:/如果沒(méi)有l(wèi)ock關(guān)鍵字的保護(hù),那么可能在執(zhí)行完if的條件判斷之后fil
25、e:/另外一個(gè)線程卻執(zhí)行了 balance=balance-amount修改了 balanee的值file:/而這個(gè)修改對(duì)這個(gè)線程是不可見的,所以可能導(dǎo)致這時(shí)if的條件已經(jīng)不成立了file:/但是,這個(gè)線程卻繼續(xù)執(zhí)行balance=balance-amount,所以導(dǎo)致balanee可能小于0f(bala nce>=am oun t)Thread.Sleep(5);bala nce=bala nce-am ount;retur namoun t;elseretur n0;/tra nsactio nrejectedin ternalvoidDoTra nsactio ns()for(i
26、nti=0;i<100;i+)Withdraw(r.Next(-50,100); intern alclassTeststatici ntern alThreadthreads=n ewThread10;publicstaticvoidMa in()Acco un tacc =n ewAcco un t(0);for(i nti=0;i<10;i+)Threadt =n ewThread( newThreadStart(acc.DoTran sact ion s);threadsi=t; for(i nti=0;i<10;i+)threadsi.Name=i.ToStri n
27、g();for(i nti=0;i<10;i+)threadsi.Start();Co nsole.ReadLi ne();而多線程公用一個(gè)對(duì)象時(shí),也會(huì)出現(xiàn)和公用代碼類似的問(wèn)題,這種問(wèn)題就不應(yīng)該使用lock關(guān)鍵字了,這里需要用到System.Threading中的一個(gè)類 Monitor,我們可以稱之為監(jiān)視器,Monitor提供了使線程共享資源的技術(shù)指導(dǎo)文件。Monitor類可以鎖定一個(gè)對(duì)象,一個(gè)線程只有得到這把鎖才可以對(duì)該對(duì)象進(jìn)行制作。對(duì)象鎖機(jī)制保證了在可能引起混亂的情況下一個(gè)時(shí)刻只有一個(gè)線程可以訪問(wèn)這個(gè)對(duì)象。Monitor必須和一個(gè)具體的對(duì)象相關(guān)聯(lián),但是由于它是一個(gè)靜態(tài)的類,所以不能使
28、用它來(lái)定義對(duì)象,而且它的所有方法都是靜態(tài)的,不能使用對(duì)象 來(lái)引用。下面代碼說(shuō)明了使用Monitor鎖定一個(gè)對(duì)象的情形:QueueoQueue=n ewQueue(); Mon itor.E nter(oQueue);/現(xiàn)在oQueue對(duì)象只能被當(dāng)前線程操縱了Mo nitor.Exit(oQueue);/ 釋放鎖如上所示,當(dāng)一個(gè)線程調(diào)用Monitor.Enter()方法鎖定一個(gè)對(duì)象時(shí),這個(gè)對(duì)象就歸它所有了,其它線程想要訪問(wèn)這個(gè)對(duì)象,只有等待它使用Monitor.Exit()方法釋放鎖。為了保證線程最終都能釋放鎖,你可以把Monitor.Exit()方法寫在try-catch-fi nally 結(jié)
29、構(gòu)中的fin ally代碼塊里。對(duì)于任何一個(gè)被Mo nitor鎖定的對(duì)象,內(nèi)存中都保存著與它相關(guān)的一些信息,其一是現(xiàn)在持有鎖的線程的引用,其二是一個(gè)預(yù)備隊(duì)列,隊(duì)列中保存了已經(jīng)準(zhǔn)備好獲取鎖的 線程,其三是一個(gè)等待隊(duì)列,隊(duì)列中保存著當(dāng)前正在等待這個(gè)對(duì)象狀態(tài)改變的隊(duì)列的引用。當(dāng)擁有對(duì)象鎖的線程 準(zhǔn)備釋放鎖時(shí),它使用Monitor.Pulse()方法通知等待隊(duì)列中的第一個(gè)線程,于是該線程被轉(zhuǎn)移到預(yù)備隊(duì)列中,當(dāng)對(duì)象鎖被釋放時(shí),在預(yù)備隊(duì)列中的線程可以立即獲得對(duì)象鎖。下面是一個(gè)展示如何使用lock關(guān)鍵字和Monitor類來(lái)實(shí)現(xiàn)線程的同步和通訊的例子,也是一個(gè)典型的生產(chǎn)者與消費(fèi)者問(wèn)題。這個(gè)例程中,生產(chǎn)者線程和
30、消費(fèi) 者線程是交替進(jìn)行的,生產(chǎn)者寫入一個(gè)數(shù),消費(fèi)者立即讀取并且顯示,我將在注釋中推薦該程序的精要所在。用 到的系統(tǒng)命名空間如下:usin gSystem; usin gSystem.Thread ing;CTGS-資料文件第一步,我們定義一個(gè)被制作的對(duì)象的類Cell,在這個(gè)類里,有兩個(gè)方法:ReadFromCell()和WriteToCell。消費(fèi)者線程將調(diào)用 ReadFromCell()讀取cellContents的內(nèi)容并且顯示出來(lái),生產(chǎn)者進(jìn)程將調(diào)用WriteToCell()方法向cellContents 寫入數(shù)據(jù)。.publicclassCellintcellContents;/Cell對(duì)
31、象里邊的內(nèi)容 boolreaderFlag=false;狀態(tài)標(biāo)志,為 true 時(shí)可以讀取,為false則正在寫入publicintReadFromCell()lock(this)/Lock關(guān)鍵字保證了什么,請(qǐng)大家看前面對(duì)lock 的推薦if(!readerFlag) 如果現(xiàn)在不可讀取 try file:/ 等待 WriteToCell 方法中調(diào)用 Monitor.Pulse()方法 Monitor.Wait(this); catch(SynchronizationLockExceptione)Console.WriteLine(e);catch(ThreadI nterruptedExcep
32、ti one)Con sole.WriteL in e(e);C on sole.WriteL in e("C on sume:O",cellContents);readerFlag=false;file:/重置 readerFlag 標(biāo)志,表示消費(fèi)行為已經(jīng)完成Monitor.Pulse(this);file:通知WriteToCell()方法(該方法在另外一個(gè)線程中執(zhí)行,等待中)returncellContents;publicvoidWriteToCell(i ntn)lock(this) if(readerFlag) try Mo nitor.Wait(this);c
33、atch(Sy nchroni zati on LockExceptione)file:/當(dāng)同步方法(指 Monitor類除Enter之外的方法)在非同步的代碼區(qū)被調(diào)用Console.WriteLine(e);catch(ThreadInterruptedExcepti。ne)file:/ 當(dāng)線程在等待狀態(tài)的時(shí)候中止Console.WriteLine(e);cellCo nte nts=n;Co nsole.WriteLi ne("Produce:0",cellCo nte nts) ;readerFlag=true;Mo nitor.Pulse(this);file:/通
34、知另外一個(gè)線程中正在等待的ReadFromCell()方法 下面定義生產(chǎn)者 CellProd和消費(fèi)者類 CellCons,它們都只有一個(gè)方法ThreadRun(),以便在 Main()函數(shù)中提供給線程的ThreadStart代理對(duì)象,作為線程的入口。publicclassCellProd Cellcell;/ 被制作的 Cell 對(duì)象 intquantity=1;生產(chǎn)者生產(chǎn)次數(shù),初始化為IpublicCelProd(Cellbox,intrequest)/ 構(gòu)造函數(shù)cell=box;quantity=request;publicvoidThreadRun()for(intlooper=1;lo
35、oper<=qua ntity;looper+)cell.WriteToCell(looper);file:生產(chǎn)者向制作對(duì)象寫入信息 publicclassCellC onsCellcell;i ntqua ntity=1;publicCellC on s(Cellbox,i ntrequest)cell=box; qua ntity=request;publicvoidThreadR un()in tvalRetur ned;for(i ntlooper=1;looper<=qua ntity;looper+)valRetur ned=cell.ReadFromCell();消費(fèi)
36、者從制作對(duì)象中讀取信息然后在下面這個(gè)類 MonitorSample的Main()函數(shù)中我們要做的就是創(chuàng)建兩個(gè)線程分別作為生產(chǎn)者和消費(fèi)者,使 用 CellProd.ThreadRun()方法和 CellCons.ThreadRun()方法對(duì)同一個(gè) Cell 對(duì)象進(jìn)行制作。publicclassM on itorSamplepublicstaticvoidMa in( Stri ngargs)in tresult=O;file:個(gè)標(biāo)志位,如果是 0表示程序沒(méi)有出錯(cuò),如果是 1表明有不對(duì)發(fā)生Cellcell=newCell(); /下面使用cell初始化CellProd和CellCons兩個(gè)類,生產(chǎn)
37、和消費(fèi)次數(shù)均為20次 CellProdprod=newCellProd(cell,20);CellCo nscons=n ewCellCo ns(cell,20);Threadproducer= newThread( newThreadStart(prod.ThreadR un);Threadc on sumer =n ewThread( newThreadStart(c on s.ThreadRun);/生產(chǎn)者線程和消費(fèi)者線程都已經(jīng)被創(chuàng)建,但是沒(méi)有開始執(zhí)行try producer.Start();c on sumer.Start();producer.J oin( );c on sumer.
38、J oin( );C on sole.ReadL in e(); catch(ThreadStateExceptione)file:/當(dāng)線程因?yàn)樗帬顟B(tài)的原因而不能執(zhí)行被請(qǐng)求的制作Console.WriteLin e(e);result=1;catch (Thread In terruptedExceptio n e)file:/當(dāng)線程在等待狀態(tài)的時(shí)候中止Con sole.WriteL in e(e);result = 1;/盡管Main()函數(shù)沒(méi)有返回值,但下面這條語(yǔ)句可以向父進(jìn)程返回執(zhí)行結(jié)果En viro nmen t.ExitCode = result;大家可以看到,在上面的例程中,同步
39、是通過(guò)等待Monitor.Pulse()來(lái)完成的。第一步生產(chǎn)者生產(chǎn)了一個(gè)值,而同一時(shí)刻消費(fèi)者處于等待狀態(tài),直到收到生產(chǎn)者的脈沖(Pulse) ”通知它生產(chǎn)已經(jīng)完成,此后消費(fèi)者進(jìn)入消費(fèi)狀態(tài),而生產(chǎn)者開始等待消費(fèi)者完成制作后將調(diào)用Monitor.Pulese()發(fā)出的 脈沖”。它的執(zhí)行結(jié)果很簡(jiǎn)單:Produce:1Con sume:1Produce:2Con sume:2Produce:3Con sume:3.Produce:20Con sume:20事實(shí)上,這個(gè)簡(jiǎn)單的例子已經(jīng)幫助我們解決了多線程應(yīng)用程序中可能出現(xiàn)的大問(wèn)題,只要領(lǐng)悟了解決線程間 沖突的基本方法,很容易把它應(yīng)用到比較復(fù)雜的程序中去。
40、.線程池和定時(shí)器 一一多線程的自動(dòng)管理在多線程的程序中,經(jīng)常會(huì)出現(xiàn)兩種情況。一種情況下,應(yīng)用程序中的線程把大部分的進(jìn)度花費(fèi)在等待狀 態(tài),等待某個(gè)事件發(fā)生,然后才能給予響應(yīng)。而另外一種情況則是線程平常都處于休眠狀態(tài),只是周期性地被喚 醒。在.netframework 里邊,我們使用ThreadPool來(lái)對(duì)付第一種情況,使用Timer來(lái)對(duì)付第二種情況。ThreadPool類提供一個(gè)由系統(tǒng)維護(hù)的線程池一一可以看作一個(gè)線程的容器,該容器需要Windows2000以上版本的系統(tǒng)支持,因?yàn)槠渲心承┓椒ㄕ{(diào)用了只有高版本的 Win dows才有的API函數(shù)。你可以使用 ThreadPool.QueueUser
41、Workltem()方法將線程安放在線程池里,該方法的原型如下:/將一個(gè)線程放進(jìn)線程池,該線程的 Start() 方法將調(diào)用 WaitCallback 代理對(duì)象代表的函數(shù)publicstaticboolQueueUserWorkltem(WaitCallback);/ 重載 的方法如下,參數(shù) object 將傳遞給WaitCallback 所代表的方法publicstaticboolQueueUserWorkltem(WaitCallback,object);要注意的是,ThreadPool類也是一個(gè)靜態(tài)類,你不能也不必要生成它的對(duì)象,而且一旦使用該方法在線程池中添加了一個(gè)項(xiàng) 目,那么該項(xiàng)目將
42、是沒(méi)有辦法取消的。在這里你無(wú)需自己建立線程,只需把你要做的工作寫成函數(shù),然后作為參 數(shù)傳遞給 ThreadPool.QueueUserWorkItem()方法就行了,傳遞的方法就是依靠WaitCallback代理對(duì)象,而線程CTGS-資料文件的建立、管理、運(yùn)行等等工作都是由系統(tǒng)自動(dòng)完成的,你無(wú)須考慮那些復(fù)雜的細(xì)節(jié)問(wèn)題,線程池的優(yōu)點(diǎn)也就在這 里體現(xiàn)出來(lái)了,就好像你是公司老板一一只需要安排工作,而不必親自動(dòng)手。.下面的例程演示了 ThreadPool的用法。第一步程序創(chuàng)建了一個(gè)ManualResetEvent對(duì)象,該對(duì)象就像一個(gè)信號(hào)燈,可以利用它的信號(hào)來(lái)通知其它線程,本例中當(dāng)線程池中所有線程工作都
43、完成以后,ManualResetEvent的對(duì)象將被設(shè)置為有信號(hào),從而通知主線程繼續(xù)運(yùn)行。它有幾個(gè)重要的方法:Reset(), Set(),Wait On e()。初始化該對(duì)象時(shí),用戶可以指定其默認(rèn)的狀態(tài)(有信號(hào)/無(wú)信號(hào)),在初始化以后,該對(duì)象將保持原來(lái)的狀態(tài)不變直到它的Reset()或者Set()方法被調(diào)用,Reset()方法將其設(shè)置為無(wú)信號(hào)狀態(tài),Set()方法將其設(shè)置為有信號(hào)狀態(tài)。WaitOne()方法使當(dāng)前線程掛起直到ManualResetEvent對(duì)象處于有信號(hào)狀態(tài),此時(shí)該線程將被激活。然后,程序?qū)⑾蚓€程池中添加工作項(xiàng),這些以函數(shù)形式提供的工作項(xiàng)被系統(tǒng)用來(lái)初始化自動(dòng)建立的線程。當(dāng)所有的
44、線程都運(yùn)行完了以后,Ma nualResetEve nt.Set() 方法被調(diào)用,因?yàn)檎{(diào)用了Ma nualResetEve nt.Wait On e()方法而處在等待狀態(tài)的主線程將接收到這個(gè)信號(hào),于是它接著往下執(zhí)行,完成后邊的工作。.using System;using System.Collect ions;using System.Thread ing;/這是用來(lái)保存信息的數(shù)據(jù)結(jié)構(gòu),將作為參數(shù)被傳遞public class SomeStatepublic int Cookie;public SomeState(i nt iCookie)Cookie = iCookie;public cla
45、ss Alphapublic Hashtable HashCo unt;public Manu alResetEve nt eve ntX;public static int iCo unt = 0;public static int iMaxCo unt = 0;public Alpha( int MaxCou nt)HashCou nt = new Hashtable(MaxCou nt);iMaxCou nt = MaxCou nt;file:/線程池里的線程將調(diào)用Beta()方法public void Beta(Object state)/輸出當(dāng)前線程的 hash編碼值和Cookie的
46、值Console.WriteLine(" 0 1 :", Thread.CurrentThread.GetHashCode(), (SomeState)state).Cookie);Console.WriteLine("HashCount.Count=0, Thread.CurrentThread.GetHashCode()=1", HashCount.Count, Thread.CurrentThread.GetHashCode();lock (HashCou nt)file:/如果當(dāng)前的Hash表中沒(méi)有當(dāng)前線程的Hash值,則添加之if (!Hash
47、Cou nt.Co nta in sKey(Thread.Curre ntThread.GetHashCode()HashCou nt.Add (Thread.Curre ntThread.GetHashCode(), 0);HashCou ntThread.Curre ntThread.GetHashCode()=(i nt)HashCou ntThread.Curre ntThread.GetHashCode()+1;int iX = 2000;Thread.Sleep(iX);/In terlocked.I ncreme nt()制作是一個(gè)原子制作,具體請(qǐng)看下面說(shuō)明In terlocke
48、d .In creme nt(ref iCo un t);if (iCou nt = iMaxCou nt)Con sole.WriteLi ne();Console.WriteLine("Setting eventX "); even tX.Set();public class SimplePoolpublic static int Main(string args)Con sole.WriteL in e("Thread Pool Sample:");bool W2K = false;int MaxCount = 10;/允許線程池中運(yùn)仃取多10個(gè)線
49、程/新建ManualResetEvent對(duì)象并且初始化為無(wú)信號(hào)狀態(tài)Manu alResetEve nt eve ntX = new Manu alResetEve nt(false);Co nsole.WriteLi ne("Queui ng 0 items to Thread Pool", MaxCou nt);Alpha oAlpha = new Alpha(MaxCou nt); file:/創(chuàng)建工作項(xiàng)/注意初始化 oAlpha對(duì)象的eventX屬性oAlpha.eve ntX = eventX;Con sole.WriteLi ne("Queue to T
50、hread Pool 0"); tryfile:/將工作項(xiàng)裝入線程池file:/這里要用到Windows 2000 以上版本才有的 API,所以可能出現(xiàn) NotSupportException 異常 ThreadPool.QueueUserWorkltem( new WaitCallback(oAlpha.Beta), new SomeState(0);W2K = true;catch (NotSupportedExcepti on)Con sole.WriteLi ne("These API's may fail whe n called on a non-Win
51、 dows 2000 system."); W2K = false;if (W2K)/如果當(dāng)前系統(tǒng)支持 ThreadPool的方法.for (int iltem=1;iltem < MaxCount;iltem+) 插入隊(duì)列兀素Co nsole.WriteLi ne("Queue to Thread Pool 0", iItem);ThreadPool.QueueUserWorkItem( new WaitCallback(oAlpha.Beta), new SomeState(iItem);Co nsole.WriteL in e("Wait i
52、ng for Thread Pool to drai n");file:/等待事件的完成,即線程調(diào)用ManualResetEvent.Set()方法eventX. Wait On e(Timeout.I nfini te,true);file:/WaitOne()方法使調(diào)用它的線程等待直到eventX.Set()方法被調(diào)用Con sole.WriteL in e("Thread Pool has bee n dra ined (Eve nt fired)");Con sole.WriteLi ne();Con sole.WriteL in e("Load
53、 across threads"); foreach(object o in oAlpha.HashCo un t.Keys)Console.WriteLine("0 1", o, oAlpha.HashCounto); Con sole.ReadL in e(); return 0;程序中有些小地方應(yīng)該引起我們的注意。SomeState類是一個(gè)保存信息的數(shù)據(jù)結(jié)構(gòu),在上面的程序中,它作為參數(shù)被傳遞給每一個(gè)線程,你很容易就能理解這個(gè),因?yàn)槟阈枰岩恍┯杏玫男畔⒎庋b起來(lái)提供給線程,而這 種方式是非常有效的。程序出現(xiàn)的InterLocked類也是專為多線程程序而存在的,
54、它提供了一些有用的原子制作,所謂原子制作就是在多線程程序中,如果這個(gè)線程調(diào)用這個(gè)制作修改一個(gè)變量,那么其他線程就不能修改這 個(gè)變量了,這跟lock關(guān)鍵字在本質(zhì)上是一樣的。我們應(yīng)該徹底地分析上面的程序,把握住線程池的本質(zhì),理解它存在的意義是什么,這樣我們才能得心應(yīng)手 地使用它。下面是該程序的輸出結(jié)果:.ThreadPoolSample:Queui ng10itemstoThreadPoolQueuetoThreadPoolO QueuetoThreadPool1. QueuetoThreadPool9Wait in gforThreadPooltodrai n980:HashCou nt.Cou
55、 nt=0,Thread.Curre ntThread.GetHashCode()=981001:HashCou nt.Cou nt=1,Thread.Curre ntThread.GetHashCode()=100982:.Sett in geve ntXThreadPoolhasbee ndrai ned(Eve ntfired)Loadacrossthreads 101210039841021與ThreadPool類不同,Timer類的作用是設(shè)置一個(gè)定時(shí)器,定時(shí)執(zhí)行用戶指定的函數(shù),而這個(gè)函數(shù)的傳遞 是靠另外一個(gè)代理對(duì)象TimerCallback,它必須在創(chuàng)建 Timer對(duì)象時(shí)就指定,并且
56、不能更改。定時(shí)器啟動(dòng)后,系統(tǒng)將自動(dòng)建立一個(gè)新的線程,并且在這個(gè)線程里執(zhí)行用戶指定的函數(shù)。下面的語(yǔ)句初始化了一個(gè)Timer對(duì)象:.Timertimer= newTimer(timerDelegate,s,1000,1000);第一個(gè)參數(shù)指定了 TimerCallback代理對(duì)象。第二個(gè)參數(shù)的意義跟上面提到的WaitCallback代理對(duì)象的一樣,作為一個(gè)傳遞數(shù)據(jù)的對(duì)象傳遞給要調(diào)用的方法。第三個(gè)參數(shù)是延遲進(jìn)度一一計(jì)時(shí)開始的時(shí)刻距現(xiàn)在的進(jìn)度,單位是毫秒。第四個(gè)參數(shù)是定時(shí)器的進(jìn)度間隔一一計(jì)時(shí)開始以后,每隔這么長(zhǎng)的一段進(jìn)度,TimerCallback所代表的方法將被調(diào)用一次,單位也是毫秒。這句話的意思就是將定時(shí)器的延遲進(jìn)度和進(jìn)度間隔都設(shè)為1秒鐘。定時(shí)器的設(shè)置是可以改變的,只要調(diào)用Timer.Change()方法,這是一個(gè)參數(shù)類型重載的方法,一般使用的
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 酒店展廳出租協(xié)議書范本
- 茶藝館茶藝培訓(xùn)與茶藝師就業(yè)合作協(xié)議
- 商業(yè)地產(chǎn)車位租賃與廣告合作合同
- 股權(quán)激勵(lì)解除及部分股權(quán)轉(zhuǎn)讓與公司業(yè)績(jī)補(bǔ)償合同
- 房屋捐贈(zèng)使用協(xié)議書范本
- 委托拆除圍擋協(xié)議書范本
- 合作雙方的協(xié)議書范本
- 股權(quán)激勵(lì)財(cái)務(wù)設(shè)計(jì)與咨詢合同
- 廠房產(chǎn)權(quán)交易居間傭金協(xié)議
- 甜品店租賃及產(chǎn)品研發(fā)合作合同
- 2024年山東普通高中學(xué)業(yè)水平等級(jí)考試化學(xué)(原卷版)
- 接警員試題題庫(kù)
- 湖南省岳陽(yáng)市2024年八年級(jí)下學(xué)期期末物理試卷附答案
- DZ∕T 0284-2015 地質(zhì)災(zāi)害排查規(guī)范(正式版)
- 《風(fēng)電功率預(yù)測(cè)功能規(guī)范》
- 關(guān)于讀后續(xù)寫的可行操作課件-高三英語(yǔ)一輪復(fù)習(xí)
- 港口企業(yè)財(cái)務(wù)風(fēng)險(xiǎn)分析報(bào)告
- 2023年貴州黔西南州專項(xiàng)招聘國(guó)企業(yè)工作人員21人考前自測(cè)高頻難、易考點(diǎn)模擬試題(共500題)含答案詳解
- 中醫(yī)護(hù)理實(shí)訓(xùn)報(bào)告總結(jié)
- 動(dòng)畫制作與電影特效課件
- 監(jiān)理抽檢表 - 08橋梁工程
評(píng)論
0/150
提交評(píng)論