版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、C 的多線程機(jī)制探索 轉(zhuǎn)注:本文中出現(xiàn)的代碼均在.net Framework RC3環(huán)境中運(yùn)行通過(guò)一.多線程的概念Windows是一個(gè)多任務(wù)的系統(tǒng),如果你使用的是windows 2000及其以上版本,你可以通過(guò)任務(wù)管理器查看當(dāng)前系統(tǒng)運(yùn)行的程序和進(jìn)程。什么是進(jìn)程呢?當(dāng)一個(gè)程序開(kāi)始運(yùn)行時(shí),它就是一個(gè)進(jìn)程,進(jìn)程所指包括運(yùn)行中的程序和程序所使用到的內(nèi)存和系統(tǒng)資源。而一個(gè)進(jìn)程又是由多個(gè)線程所組成的,線程是程序中的一個(gè)執(zhí)行流,每個(gè)線程都有自己的專有寄存器(棧指針、程序計(jì)數(shù)器等),但代碼區(qū)是共享的,即不同的線程可以執(zhí)行同樣的函數(shù)。多線程是指程序中包含多個(gè)執(zhí)行流,即在一個(gè)程序中可以同時(shí)運(yùn)行多個(gè)不同的線程來(lái)執(zhí)行
2、不同的任務(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í)到線程本身可能影響系統(tǒng)性能的不利方面,以正確使用線程:線程也是程序,所以線程需要占用內(nèi)存,線程越多占用內(nèi)存也越多多線程需要協(xié)調(diào)和管理,所以需要CPU時(shí)間跟蹤線程線程之間對(duì)共享資
3、源的訪問(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è)程序中也可以只用一個(gè)線程去做事,事實(shí)上,一些過(guò)時(shí)的語(yǔ)言如fortune,basic都是如此,但是象一個(gè)人的公司一樣,效率很低,如果做大程序,效率更低-事實(shí)上現(xiàn)在幾乎沒(méi)有單線程的商業(yè)軟件。公司的職
4、員越多,老板就得發(fā)越多的薪水給他們,還得耗費(fèi)大量精力去管理他們,協(xié)調(diào)他們之間的矛盾和利益;程序也是如此,線程越多耗費(fèi)的資源也越多,需要CPU時(shí)間去跟蹤線程,還得解決諸如死鎖,同步等問(wèn)題??傊绻悴幌肽愕墓颈环Q為皮包公司,你就得多幾個(gè)員工;如果你不想讓你的程序顯得稚氣,就在你的程序里引入多線程吧!本文將對(duì)C#編程中的多線程機(jī)制進(jìn)行探討,通過(guò)一些實(shí)例解決對(duì)線程的控制,多線程間通訊等問(wèn)題。為了省去創(chuàng)建GUI那些繁瑣的步驟,更清晰地逼近線程的本質(zhì),下面所有的程序都是控制臺(tái)程序,程序最后的Console.ReadLine()是為了使程序中途停下來(lái),以便看清楚執(zhí)行過(guò)程中的輸出。好了,廢話少說(shuō),讓我們
5、來(lái)體驗(yàn)一下多線程的C#吧!二.操縱一個(gè)線程任何程序在執(zhí)行時(shí),至少有一個(gè)主線程,下面這段小程序可以給讀者一個(gè)直觀的印象:/SystemThread.cs using System;using System.Threading;namespace ThreadTestclass RunItSTAThreadstatic void Main(string args)Thread.CurrentThread.Name=System Thread;/給當(dāng)前線程起名為System ThreadConsole.WriteLine(Thread.CurrentThread.Name+Status:+Threa
6、d.CurrentThread.ThreadState);Console.ReadLine();編譯執(zhí)行后你看到了什么?是的,程序?qū)a(chǎn)生如下輸出:System Threads Status:Running在這里,我們通過(guò)Thread類的靜態(tài)屬性CurrentThread獲取了當(dāng)前執(zhí)行的線程,對(duì)其Name屬性賦值System Thread,最后還輸出了它的當(dāng)前狀態(tài)(ThreadState)。所謂靜態(tài)屬性,就是這個(gè)類所有對(duì)象所公有的屬性,不管你創(chuàng)建了多少個(gè)這個(gè)類的實(shí)例,但是類的靜態(tài)屬性在內(nèi)存中只有一個(gè)。很容易理解CurrentThread為什么是靜態(tài)的-雖然有多個(gè)線程同時(shí)存在,但是在某一個(gè)時(shí)刻,C
7、PU只能執(zhí)行其中一個(gè)。就像上面程序所演示的,我們通過(guò)Thread類來(lái)創(chuàng)建和控制線程。注意到程序的頭部,我們使用了如下命名空間:using System;using System.Threading;在.net framework class library中,所有與多線程機(jī)制應(yīng)用相關(guān)的類都是放在System.Threading命名空間中的。其中提供Thread類用于創(chuàng)建線程,ThreadPool類用于管理線程池等等,此外還提供解決了線程執(zhí)行安排,死鎖,線程間通訊等實(shí)際問(wèn)題的機(jī)制。如果你想在你的應(yīng)用程序中使用多線程,就必須包含這個(gè)類。Thread類有幾個(gè)至關(guān)重要的方法,描述如下:Start():
8、啟動(dòng)線程Sleep(int):靜態(tài)方法,暫停當(dāng)前線程指定的毫秒數(shù)Abort():通常使用該方法來(lái)終止一個(gè)線程Suspend():該方法并不終止未完成的線程,它僅僅掛起線程,以后還可恢復(fù)。Resume():恢復(fù)被Suspend()方法掛起的線程的執(zhí)行1下面我們就動(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()方法后,線程就開(kāi)始執(zhí)行ThreadStar
9、t所代表或者說(shuō)指向的函數(shù)。打開(kāi)你的VS.net,新建一個(gè)控制臺(tái)應(yīng)用程序(Console Application),下面這些代碼將讓你體味到完全控制一個(gè)線程的無(wú)窮樂(lè)趣!/ThreadTest.cs using System;using System.Threading;namespace ThreadTestpublic class Alphapublic void Beta()while(true)Console.WriteLine(Alpha.Beta is running in its own thread.);public class Simplepublic static int Ma
10、in()Console.WriteLine(Thread Start/Stop/Join Sample);Alpha oAlpha=new Alpha();這里創(chuàng)建一個(gè)線程,使之執(zhí)行Alpha類的Beta()方法Thread oThread=new Thread(new ThreadStart(oAlpha.Beta);oThread.Start();while(!oThread.IsAlive);Thread.Sleep(1);oThread.Abort();oThread.Join();Console.WriteLine();Console.WriteLine(Alpha.Beta has
11、 finished);tryConsole.WriteLine(Try to restart the Alpha.Beta thread);oThread.Start();catch(ThreadStateException)Console.Write(ThreadStateException trying to restart Alpha.Beta.);Console.WriteLine(Expected since aborted threads cannot be restarted.);Console.ReadLine();return 0;這段程序包含兩個(gè)類Alpha和Simple,
12、在創(chuàng)建線程oThread時(shí)我們用指向Alpha.Beta()方法的初始化了ThreadStart代理(delegate)對(duì)象,當(dāng)我們創(chuàng)建的線程oThread調(diào)用oThread.Start()方法啟動(dòng)時(shí),實(shí)際上程序運(yùn)行的是Alpha.Beta()方法:Alpha oAlpha=new Alpha();Thread oThread=new Thread(new ThreadStart(oAlpha.Beta);oThread.Start();然后在Main()函數(shù)的while循環(huán)中,我們使用靜態(tài)方法Thread.Sleep()讓主線程停了1ms,這段時(shí)間CPU轉(zhuǎn)向執(zhí)行線程oThread。然后我們?cè)?/p>
13、圖用Thread.Abort()方法終止線程oThread,注意后面的oThread.Join(),Thread.Join()方法使主線程等待,直到oThread線程結(jié)束。你可以給Thread.Join()方法指定一個(gè)int型的參數(shù)作為等待的最長(zhǎng)時(shí)間。之后,我們?cè)噲D用Thread.Start()方法重新啟動(dòng)線程oThread,但是顯然Abort()方法帶來(lái)的后果是不可恢復(fù)的終止線程,所以最后程序會(huì)拋出ThreadStateException異常。程序最后得到的結(jié)果將如下圖:在這里我們要注意的是其它線程都是依附于Main()函數(shù)所在的線程的,Main()函數(shù)是C#程序的入口,起始線程可以稱之為主
14、線程,如果所有的前臺(tái)線程都停止了,那么主線程可以終止,而所有的后臺(tái)線程都將無(wú)條件終止。而所有的線程雖然在微觀上是串行執(zhí)行的,但是在宏觀上你完全可以認(rèn)為它們?cè)诓⑿袌?zhí)行。讀者一定注意到了Thread.ThreadState這個(gè)屬性,這個(gè)屬性代表了線程運(yùn)行時(shí)狀態(tài),在不同的情況下有不同的值,于是我們有時(shí)候可以通過(guò)對(duì)該值的判斷來(lái)設(shè)計(jì)程序流程。ThreadState在各種情況下的可能取值如下:Aborted:線程已停止AbortRequested:線程的Thread.Abort()方法已被調(diào)用,但是線程還未停止Background:線程在后臺(tái)執(zhí)行,與屬性Thread.IsBackground有關(guān)Runni
15、ng:線程正在正常運(yùn)行Stopped:線程已經(jīng)被停止StopRequested:線程正在被要求停止Suspended:線程已經(jīng)被掛起(此狀態(tài)下,可以通過(guò)調(diào)用Resume()方法重新運(yùn)行)SuspendRequested:線程正在要求被掛起,但是未來(lái)得及響應(yīng)Unstarted:未調(diào)用Thread.Start()開(kāi)始線程的運(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)線程不妨礙程序的終止。一旦一個(gè)進(jìn)程
16、所有的前臺(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時(shí)間時(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=ThreadPriority.Lowest;通過(guò)設(shè)定線程的優(yōu)先級(jí),我們可以安
17、排一些相對(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ū)是共享的,即每個(gè)線程都可以執(zhí)行相同的函數(shù)。但是多線程環(huán)境下,可能帶來(lái)的問(wèn)題
18、就是幾個(gè)線程同時(shí)執(zhí)行一個(gè)函數(shù),導(dǎo)致數(shù)據(jù)的混亂,產(chǎn)生不可預(yù)料的結(jié)果,因此我們必須避免這種情況的發(fā)生。C#提供了一個(gè)關(guān)鍵字lock,它可以把一段代碼定義為互斥段(critical section),互斥段在一個(gè)時(shí)刻內(nèi)只允許一個(gè)線程進(jìn)入執(zhí)行,而其他線程必須等待。在C#中,關(guān)鍵字lock定義如下:lock(expression)statement_block expression代表你希望跟蹤的對(duì)象,通常是對(duì)象引用。一般地,如果你想保護(hù)一個(gè)類的實(shí)例,你可以使用this;如果你希望保護(hù)一個(gè)靜態(tài)變量(如互斥代碼段在一個(gè)靜態(tài)方法內(nèi)部),一般使用類名就可以了。而statement_block就是互斥段的代碼,
19、這段代碼在一個(gè)時(shí)刻內(nèi)只可能被一個(gè)線程執(zhí)行。下面是一個(gè)使用lock關(guān)鍵字的典型例子,我將在注釋里向大家說(shuō)明lock關(guān)鍵字的用法和用途:/lock.cs using System;using System.Threading;internal class Accountint balance;Random r=new Random();internal Account(int initial)balance=initial;internal int Withdraw(int amount)if(balance 0)如果balance小于0則拋出異常throw new Exception(Negat
20、ive Balance);/下面的代碼保證在當(dāng)前線程修改balance的值完成之前/不會(huì)有其他線程也執(zhí)行這段代碼來(lái)修改balance的值/因此,balance的值是不可能小于0的lock(this)Console.WriteLine(Current Thread:+Thread.CurrentThread.Name);如果沒(méi)有l(wèi)ock關(guān)鍵字的保護(hù),那么可能在執(zhí)行完if的條件判斷之后另外一個(gè)線程卻執(zhí)行了balance=balance-amount修改了balance的值而這個(gè)修改對(duì)這個(gè)線程是不可見(jiàn)的,所以可能導(dǎo)致這時(shí)if的條件已經(jīng)不成立了但是,這個(gè)線程卻繼續(xù)執(zhí)行balance=balance-a
21、mount,所以導(dǎo)致balance可能小于0 if(balance=amount)Thread.Sleep(5);balance=balance-amount;return amount;elsereturn 0;/transaction rejectedinternal void DoTransactions()for(int i=0;i 100;i+)Withdraw(r.Next(-50,100);internal class Teststatic internal Thread threads=new Thread10;public static void Main()Account
22、acc=new Account(0);for(int i=0;i 10;i+)Thread t=new Thread(new ThreadStart(acc.DoTransactions);threadsi=t;for(int i=0;i 10;i+)threadsi.Name=i.ToString();for(int i=0;i 10;i+)threadsi.Start();Console.ReadLine();而多線程公用一個(gè)對(duì)象時(shí),也會(huì)出現(xiàn)和公用代碼類似的問(wèn)題,這種問(wèn)題就不應(yīng)該使用lock關(guān)鍵字了,這里需要用到System.Threading中的一個(gè)類Monitor,我們可以稱之為監(jiān)視器
23、,Monitor提供了使線程共享資源的方案。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)的類,所以不能使用它來(lái)定義對(duì)象,而且它的所有方法都是靜態(tài)的,不能使用對(duì)象來(lái)引用。下面代碼說(shuō)明了使用Monitor鎖定一個(gè)對(duì)象的情形:.Queue oQueue=new Queue();.Monitor.Enter(oQueue);./現(xiàn)在oQueue對(duì)象只能被當(dāng)前線程操縱了Monitor.Exit(oQueue);/釋放鎖如上所示,當(dāng)一
24、個(gè)線程調(diào)用Monitor.Enter()方法鎖定一個(gè)對(duì)象時(shí),這個(gè)對(duì)象就歸它所有了,其它線程想要訪問(wèn)這個(gè)對(duì)象,只有等待它使用Monitor.Exit()方法釋放鎖。為了保證線程最終都能釋放鎖,你可以把Monitor.Exit()方法寫在try-catch-finally結(jié)構(gòu)中的finally代碼塊里。對(duì)于任何一個(gè)被Monitor鎖定的對(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(
25、)方法通知等待隊(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)者線程和消費(fèi)者線程是交替進(jìn)行的,生產(chǎn)者寫入一個(gè)數(shù),消費(fèi)者立即讀取并且顯示,我將在注釋中介紹該程序的精要所在。用到的系統(tǒng)命名空間如下:using System;using System.Threading;首先,我們定義一個(gè)被操作的對(duì)象的類Cell,在這個(gè)類里,有兩個(gè)方法:ReadFromCell()和WriteToCell。消費(fèi)者線程將調(diào)用Re
26、adFromCell()讀取cellContents的內(nèi)容并且顯示出來(lái),生產(chǎn)者進(jìn)程將調(diào)用WriteToCell()方法向cellContents寫入數(shù)據(jù)。public class Cellint cellContents;/Cell對(duì)象里邊的內(nèi)容bool readerFlag=false;/狀態(tài)標(biāo)志,為true時(shí)可以讀取,為false則正在寫入public int ReadFromCell()lock(this)/Lock關(guān)鍵字保證了什么,請(qǐng)大家看前面對(duì)lock的介紹if(!readerFlag)/如果現(xiàn)在不可讀取try等待WriteToCell方法中調(diào)用Monitor.Pulse()方法Mo
27、nitor.Wait(this);catch(SynchronizationLockException e)Console.WriteLine(e);catch(ThreadInterruptedException e)Console.WriteLine(e);Console.WriteLine(Consume:0,cellContents);readerFlag=false;重置readerFlag標(biāo)志,表示消費(fèi)行為已經(jīng)完成Monitor.Pulse(this);通知WriteToCell()方法(該方法在另外一個(gè)線程中執(zhí)行,等待中)return cellContents;public vo
28、id WriteToCell(int n)lock(this)if(readerFlag)tryMonitor.Wait(this);catch(SynchronizationLockException e)當(dāng)同步方法(指Monitor類除Enter之外的方法)在非同步的代碼區(qū)被調(diào)用Console.WriteLine(e);catch(ThreadInterruptedException e)當(dāng)線程在等待狀態(tài)的時(shí)候中止Console.WriteLine(e);cellContents=n;Console.WriteLine(Produce:0,cellContents);readerFlag=
29、true;Monitor.Pulse(this);通知另外一個(gè)線程中正在等待的ReadFromCell()方法下面定義生產(chǎn)者CellProd和消費(fèi)者類CellCons,它們都只有一個(gè)方法ThreadRun(),以便在Main()函數(shù)中提供給線程的ThreadStart代理對(duì)象,作為線程的入口。public class CellProdCell cell;/被操作的Cell對(duì)象int quantity=1;/生產(chǎn)者生產(chǎn)次數(shù),初始化為1 public CellProd(Cell box,int request)/構(gòu)造函數(shù)cell=box;quantity=request;public void T
30、hreadRun()for(int looper=1;looper=quantity;looper+)cell.WriteToCell(looper);生產(chǎn)者向操作對(duì)象寫入信息public class CellConsCell cell;int quantity=1;public CellCons(Cell box,int request)cell=box;quantity=request;public void ThreadRun()int valReturned;for(int looper=1;looper=quantity;looper+)valReturned=cell.ReadFr
31、omCell();/消費(fèi)者從操作對(duì)象中讀取信息然后在下面這個(gè)類MonitorSample的Main()函數(shù)中我們要做的就是創(chuàng)建兩個(gè)線程分別作為生產(chǎn)者和消費(fèi)者,使用CellProd.ThreadRun()方法和CellCons.ThreadRun()方法對(duì)同一個(gè)Cell對(duì)象進(jìn)行操作。public class MonitorSamplepublic static void Main(String args)int result=0;一個(gè)標(biāo)志位,如果是0表示程序沒(méi)有出錯(cuò),如果是1表明有錯(cuò)誤發(fā)生Cell cell=new Cell();/下面使用cell初始化CellProd和CellCons兩個(gè)類,
32、生產(chǎn)和消費(fèi)次數(shù)均為20次CellProd prod=new CellProd(cell,20);CellCons cons=new CellCons(cell,20);Thread producer=new Thread(new ThreadStart(prod.ThreadRun);Thread consumer=new Thread(new ThreadStart(cons.ThreadRun);/生產(chǎn)者線程和消費(fèi)者線程都已經(jīng)被創(chuàng)建,但是沒(méi)有開(kāi)始執(zhí)行tryproducer.Start();consumer.Start();producer.Join();consumer.Join();Co
33、nsole.ReadLine();catch(ThreadStateException e)當(dāng)線程因?yàn)樗帬顟B(tài)的原因而不能執(zhí)行被請(qǐng)求的操作Console.WriteLine(e);result=1;catch(ThreadInterruptedException e)當(dāng)線程在等待狀態(tài)的時(shí)候中止Console.WriteLine(e);result=1;/盡管Main()函數(shù)沒(méi)有返回值,但下面這條語(yǔ)句可以向父進(jìn)程返回執(zhí)行結(jié)果Environment.ExitCode=result;大家可以看到,在上面的例程中,同步是通過(guò)等待Monitor.Pulse()來(lái)完成的。首先生產(chǎn)者生產(chǎn)了一個(gè)值,而同一時(shí)刻
34、消費(fèi)者處于等待狀態(tài),直到收到生產(chǎn)者的脈沖(Pulse)通知它生產(chǎn)已經(jīng)完成,此后消費(fèi)者進(jìn)入消費(fèi)狀態(tài),而生產(chǎn)者開(kāi)始等待消費(fèi)者完成操作后將調(diào)用Monitor.Pulese()發(fā)出的脈沖。它的執(zhí)行結(jié)果很簡(jiǎn)單:Produce:1 Consume:1 Produce:2 Consume:2 Produce:3 Consume:3.Produce:20 Consume:20事實(shí)上,這個(gè)簡(jiǎn)單的例子已經(jīng)幫助我們解決了多線程應(yīng)用程序中可能出現(xiàn)的大問(wèn)題,只要領(lǐng)悟了解決線程間沖突的基本方法,很容易把它應(yīng)用到比較復(fù)雜的程序中去。四、線程池和定時(shí)器-多線程的自動(dòng)管理在多線程的程序中,經(jīng)常會(huì)出現(xiàn)兩種情況。一種情況下,應(yīng)用程
35、序中的線程把大部分的時(shí)間花費(fèi)在等待狀態(tài),等待某個(gè)事件發(fā)生,然后才能給予響應(yīng);而另外一種情況則是線程平常都處于休眠狀態(tài),只是周期性地被喚醒。在.net framework里邊,我們使用ThreadPool來(lái)對(duì)付第一種情況,使用Timer來(lái)對(duì)付第二種情況。ThreadPool類提供一個(gè)由系統(tǒng)維護(hù)的線程池-可以看作一個(gè)線程的容器,該容器需要Windows 2000以上版本的系統(tǒng)支持,因?yàn)槠渲心承┓椒ㄕ{(diào)用了只有高版本的Windows才有的API函數(shù)。你可以使用ThreadPool.QueueUserWorkItem()方法將線程安放在線程池里,該方法的原型如下:/將一個(gè)線程放進(jìn)線程池,該線程的Star
36、t()方法將調(diào)用WaitCallback代理對(duì)象代表的函數(shù)public static bool QueueUserWorkItem(WaitCallback);/重載的方法如下,參數(shù)object將傳遞給WaitCallback所代表的方法public static bool QueueUserWorkItem(WaitCallback,object);要注意的是,ThreadPool類也是一個(gè)靜態(tài)類,你不能也不必要生成它的對(duì)象,而且一旦使用該方法在線程池中添加了一個(gè)項(xiàng)目,那么該項(xiàng)目將是沒(méi)有辦法取消的。在這里你無(wú)需自己建立線程,只需把你要做的工作寫成函數(shù),然后作為參數(shù)傳遞給ThreadPool.
37、QueueUserWorkItem()方法就行了,傳遞的方法就是依靠WaitCallback代理對(duì)象,而線程的建立、管理、運(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)線程池中所有線程工作都完成以后,ManualResetEvent的對(duì)象將被設(shè)置為有信號(hào),從而通知主線程繼續(xù)運(yùn)行。它有幾個(gè)重要的方法:Reset(),Set(),Wai
38、tOne()。初始化該對(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)所有的線程都運(yùn)行完了以后,ManualResetEvent.Set()方法被調(diào)用,因?yàn)檎{(diào)用了ManualResetEvent.WaitOne()方法而處在等
39、待狀態(tài)的主線程將接收到這個(gè)信號(hào),于是它接著往下執(zhí)行,完成后邊的工作。using System;using System.Collections;using System.Threading;/這是用來(lái)保存信息的數(shù)據(jù)結(jié)構(gòu),將作為參數(shù)被傳遞public class SomeStatepublic int Cookie;public SomeState(int iCookie)Cookie=iCookie;public class Alphapublic Hashtable HashCount;public ManualResetEvent eventX;public static int iCou
40、nt=0;public static int iMaxCount=0;public Alpha(int MaxCount)HashCount=new Hashtable(MaxCount);iMaxCount=MaxCount;線程池里的線程將調(diào)用Beta()方法public void Beta(Object state)/輸出當(dāng)前線程的hash編碼值和Cookie的值Console.WriteLine(01:,Thread.CurrentThread.GetHashCode(),(SomeState)state).Cookie);Console.WriteLine(HashCount.Cou
41、nt=0,Thread.CurrentThread.GetHashCode()=1,HashCount.Count,Thread.CurrentThread.GetHashCode();lock(HashCount)如果當(dāng)前的Hash表中沒(méi)有當(dāng)前線程的Hash值,則添加之if(!HashCount.ContainsKey(Thread.CurrentThread.GetHashCode()HashCount.Add(Thread.CurrentThread.GetHashCode(),0);HashCountThread.CurrentThread.GetHashCode()=(int)Has
42、hCountThread.CurrentThread.GetHashCode()+1;int iX=2000;Thread.Sleep(iX);/Interlocked.Increment()操作是一個(gè)原子操作,具體請(qǐng)看下面說(shuō)明Interlocked.Increment(ref iCount);if(iCount=iMaxCount)Console.WriteLine();Console.WriteLine(Setting eventX);eventX.Set();public class SimplePoolpublic static int Main(string args)Console
43、.WriteLine(Thread Pool Sample:);bool W2K=false;int MaxCount=10;/允許線程池中運(yùn)行最多10個(gè)線程/新建ManualResetEvent對(duì)象并且初始化為無(wú)信號(hào)狀態(tài)ManualResetEvent eventX=new ManualResetEvent(false);Console.WriteLine(Queuing0items to Thread Pool,MaxCount);Alpha oAlpha=new Alpha(MaxCount);創(chuàng)建工作項(xiàng)/注意初始化oAlpha對(duì)象的eventX屬性oAlpha.eventX=event
44、X;Console.WriteLine(Queue to Thread Pool 0);try將工作項(xiàng)裝入線程池這里要用到Windows 2000以上版本才有的API,所以可能出現(xiàn)NotSupportException異常ThreadPool.QueueUserWorkItem(new WaitCallback(oAlpha.Beta),new SomeState(0);W2K=true;catch(NotSupportedException)Console.WriteLine(These APIs may fail when called on anon-Windows 2000 syste
45、m.);W2K=false;if(W2K)/如果當(dāng)前系統(tǒng)支持ThreadPool的方法.for(int iItem=1;iItem MaxCount;iItem+)/插入隊(duì)列元素Console.WriteLine(Queue to Thread Pool0,iItem);ThreadPool.QueueUserWorkItem(new WaitCallback(oAlpha.Beta),new SomeState(iItem);Console.WriteLine(Waiting for Thread Pool to drain);等待事件的完成,即線程調(diào)用ManualResetEvent.Se
46、t()方法eventX.WaitOne(Timeout.Infinite,true);()方法使調(diào)用它的線程等待直到eventX.Set()方法被調(diào)用Console.WriteLine(Thread Pool has been drained(Event fired);Console.WriteLine();Console.WriteLine(Load across threads);foreach(object oin oAlpha.HashCount.Keys)Console.WriteLine(01,o,oAlpha.HashCounto);Console.ReadLine();retu
47、rn 0;程序中有些小地方應(yīng)該引起我們的注意。SomeState類是一個(gè)保存信息的數(shù)據(jù)結(jié)構(gòu),在上面的程序中,它作為參數(shù)被傳遞給每一個(gè)線程,你很容易就能理解這個(gè),因?yàn)槟阈枰岩恍┯杏玫男畔⒎庋b起來(lái)提供給線程,而這種方式是非常有效的。程序出現(xiàn)的InterLocked類也是專為多線程程序而存在的,它提供了一些有用的原子操作,所謂原子操作就是在多線程程序中,如果這個(gè)線程調(diào)用這個(gè)操作修改一個(gè)變量,那么其他線程就不能修改這個(gè)變量了,這跟lock關(guān)鍵字在本質(zhì)上是一樣的。我們應(yīng)該徹底地分析上面的程序,把握住線程池的本質(zhì),理解它存在的意義是什么,這樣我們才能得心應(yīng)手地使用它。下面是該程序的輸出結(jié)果:Thread
48、 Pool Sample:Queuing 10 items to Thread Pool Queue to Thread Pool 0Queue to Thread Pool 1.Queue to Thread Pool 9Waiting for Thread Pool to drain 98 0:HashCount.Count=0,Thread.CurrentThread.GetHashCode()=98 100 1:HashCount.Count=1,Thread.CurrentThread.GetHashCode()=100 98 2:.Setting eventX Thread Poo
49、l has been drained(Event fired)Load across threads 101 2100 398 4102 1與ThreadPool類不同,Timer類的作用是設(shè)置一個(gè)定時(shí)器,定時(shí)執(zhí)行用戶指定的函數(shù),而這個(gè)函數(shù)的傳遞是靠另外一個(gè)代理對(duì)象TimerCallback,它必須在創(chuàng)建Timer對(duì)象時(shí)就指定,并且不能更改。定時(shí)器啟動(dòng)后,系統(tǒng)將自動(dòng)建立一個(gè)新的線程,并且在這個(gè)線程里執(zhí)行用戶指定的函數(shù)。下面的語(yǔ)句初始化了一個(gè)Timer對(duì)象:Timer timer=new Timer(timerDelegate,s,1000,1000);第一個(gè)參數(shù)指定了TimerCallback
50、代理對(duì)象;第二個(gè)參數(shù)的意義跟上面提到的WaitCallback代理對(duì)象的一樣,作為一個(gè)傳遞數(shù)據(jù)的對(duì)象傳遞給要調(diào)用的方法;第三個(gè)參數(shù)是延遲時(shí)間-計(jì)時(shí)開(kāi)始的時(shí)刻距現(xiàn)在的時(shí)間,單位是毫秒;第四個(gè)參數(shù)是定時(shí)器的時(shí)間間隔-計(jì)時(shí)開(kāi)始以后,每隔這么長(zhǎng)的一段時(shí)間,TimerCallback所代表的方法將被調(diào)用一次,單位也是毫秒。這句話的意思就是將定時(shí)器的延遲時(shí)間和時(shí)間間隔都設(shè)為1秒鐘。定時(shí)器的設(shè)置是可以改變的,只要調(diào)用Timer.Change()方法,這是一個(gè)參數(shù)類型重載的方法,一般使用的原型如下:public bool Change(long,long);下面這段代碼將前邊設(shè)置的定時(shí)器修改了一下:timer
51、.Change(10000,2000);很顯然,定時(shí)器timer的時(shí)間間隔被重新設(shè)置為2秒,停止計(jì)時(shí)10秒后生效。下面這段程序演示了Timer類的用法。using System;using System.Threading;class TimerExampleStatepublic int counter=0;public Timer tmr;class Apppublic static void Main()TimerExampleState s=new TimerExampleState();/創(chuàng)建代理對(duì)象TimerCallback,該代理將被定時(shí)調(diào)用TimerCallback timer
52、Delegate=new TimerCallback(CheckStatus);/創(chuàng)建一個(gè)時(shí)間間隔為1s的定時(shí)器Timer timer=new Timer(timerDelegate,s,1000,1000);s.tmr=timer;/主線程停下來(lái)等待Timer對(duì)象的終止while(s.tmr!=null)Thread.Sleep(0);Console.WriteLine(Timer example done.);Console.ReadLine();下面是被定時(shí)調(diào)用的方法static void CheckStatus(Object state)TimerExampleState s=(Tim
53、erExampleState)state;s.counter+;Console.WriteLine(0Checking Status1.,DateTime.Now.TimeOfDay,s.counter);if(s.counter=5)使用Change方法改變了時(shí)間間隔(s.tmr).Change(10000,2000);Console.WriteLine(changed.);if(s.counter=10)Console.WriteLine(disposing of timer.);s.tmr.Dispose();s.tmr=null;程序首先創(chuàng)建了一個(gè)定時(shí)器,它將在創(chuàng)建1秒之后開(kāi)始每隔1秒
54、調(diào)用一次CheckStatus()方法,當(dāng)調(diào)用5次以后,在CheckStatus()方法中修改了時(shí)間間隔為2秒,并且指定在10秒后重新開(kāi)始。當(dāng)計(jì)數(shù)達(dá)到10次,調(diào)用Timer.Dispose()方法刪除了timer對(duì)象,主線程于是跳出循環(huán),終止程序。程序執(zhí)行的結(jié)果如下:上面就是對(duì)ThreadPool和Timer兩個(gè)類的簡(jiǎn)單介紹,充分利用系統(tǒng)提供的功能,可以為我們省去很多時(shí)間和精力-特別是對(duì)很容易出錯(cuò)的多線程程序。同時(shí)我們也可以看到.net Framework強(qiáng)大的內(nèi)置對(duì)象,這些將對(duì)我們的編程帶來(lái)莫大的方便。、互斥對(duì)象-更加靈活的同步方式有時(shí)候你會(huì)覺(jué)得上面介紹的方法好像不夠用,對(duì),我們解決了代碼和
55、資源的同步問(wèn)題,解決了多線程自動(dòng)化管理和定時(shí)觸發(fā)的問(wèn)題,但是如何控制多個(gè)線程相互之間的聯(lián)系呢?例如我要到餐廳吃飯,在吃飯之前我先得等待廚師把飯菜做好,之后我開(kāi)始吃飯,吃完我還得付款,付款方式可以是現(xiàn)金,也可以是信用卡,付款之后我才能離開(kāi)。分析一下這個(gè)過(guò)程,我吃飯可以看作是主線程,廚師做飯又是一個(gè)線程,服務(wù)員用信用卡收款和收現(xiàn)金可以看作另外兩個(gè)線程,大家可以很清楚地看到其中的關(guān)系-我吃飯必須等待廚師做飯,然后等待兩個(gè)收款線程之中任意一個(gè)的完成,然后我吃飯這個(gè)線程可以執(zhí)行離開(kāi)這個(gè)步驟,于是我吃飯才算結(jié)束了。事實(shí)上,現(xiàn)實(shí)中有著比這更復(fù)雜的聯(lián)系,我們?cè)鯓硬拍芎芎玫乜刂扑鼈兌划a(chǎn)生沖突和重復(fù)呢?這種情況
56、下,我們需要用到互斥對(duì)象,即System.Threading命名空間中的Mutex類。大家一定坐過(guò)出租車吧,事實(shí)上我們可以把Mutex看作一個(gè)出租車,那么乘客就是線程了,乘客首先得等車,然后上車,最后下車,當(dāng)一個(gè)乘客在車上時(shí),其他乘客就只有等他下車以后才可以上車。而線程與Mutex對(duì)象的關(guān)系也正是如此,線程使用Mutex.WaitOne()方法等待Mutex對(duì)象被釋放,如果它等待的Mutex對(duì)象被釋放了,它就自動(dòng)擁有這個(gè)對(duì)象,直到它調(diào)用Mutex.ReleaseMutex()方法釋放這個(gè)對(duì)象,而在此期間,其他想要獲取這個(gè)Mutex對(duì)象的線程都只有等待。下面這個(gè)例子使用了Mutex對(duì)象來(lái)同步四個(gè)
57、線程,主線程等待四個(gè)線程的結(jié)束,而這四個(gè)線程的運(yùn)行又是與兩個(gè)Mutex對(duì)象相關(guān)聯(lián)的。其中還用到AutoResetEvent類的對(duì)象,如同上面提到的ManualResetEvent對(duì)象一樣,大家可以把它簡(jiǎn)單地理解為一個(gè)信號(hào)燈,使用AutoResetEvent.Set()方法可以設(shè)置它為有信號(hào)狀態(tài),而使用AutoResetEvent.Reset()方法把它設(shè)置為無(wú)信號(hào)狀態(tài)。這里用它的有信號(hào)狀態(tài)來(lái)表示一個(gè)線程的結(jié)束。/Mutex.cs using System;using System.Threading;public class MutexSamplestatic Mutex gM1;static
58、 Mutex gM2;const int ITERS=100;static AutoResetEvent Event1=new AutoResetEvent(false);static AutoResetEvent Event2=new AutoResetEvent(false);static AutoResetEvent Event3=new AutoResetEvent(false);static AutoResetEvent Event4=new AutoResetEvent(false);public static void Main(String args)Console.WriteLine(Mutex Sample.);/創(chuàng)建一個(gè)Mutex對(duì)象,并且命名為MyMutex gM1=new Mutex(true,MyMutex);/創(chuàng)建一個(gè)未命名的Mutex對(duì)象.gM2=new Mutex(true);Console.WriteLine(-Main
溫馨提示
- 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ì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 恒昌元生產(chǎn)管理制度
- 珠寶生產(chǎn)銷售部制度
- 委托生產(chǎn)留樣管理制度
- 生產(chǎn)應(yīng)用系統(tǒng)管理制度
- 生產(chǎn)工作考核制度
- 安全生產(chǎn)法律培訓(xùn)制度
- 生產(chǎn)經(jīng)營(yíng)委員會(huì)制度
- 生產(chǎn)成本測(cè)算管理制度
- 電石生產(chǎn)部管理制度
- 生產(chǎn)設(shè)備檢查管理制度
- 商業(yè)廣場(chǎng)物管費(fèi)測(cè)算表
- 申論范文寶典
- 【一例擴(kuò)張型心肌病合并心力衰竭患者的個(gè)案護(hù)理】5400字【論文】
- 四川橋梁工程系梁專項(xiàng)施工方案
- 貴州省納雍縣水東鄉(xiāng)水東鉬鎳礦采礦權(quán)評(píng)估報(bào)告
- GB.T19418-2003鋼的弧焊接頭 缺陷質(zhì)量分級(jí)指南
- 污水管網(wǎng)監(jiān)理規(guī)劃
- 2023年杭州臨平環(huán)境科技有限公司招聘筆試題庫(kù)及答案解析
- 《看圖猜成語(yǔ)》課件
- LF爐機(jī)械設(shè)備安裝施工方案
- 企業(yè)三級(jí)安全生產(chǎn)標(biāo)準(zhǔn)化評(píng)定表(新版)
評(píng)論
0/150
提交評(píng)論