C-多線程的詳細(xì)教程_第1頁
C-多線程的詳細(xì)教程_第2頁
C-多線程的詳細(xì)教程_第3頁
C-多線程的詳細(xì)教程_第4頁
C-多線程的詳細(xì)教程_第5頁
已閱讀5頁,還剩9頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)

文檔簡介

1/1C_多線程的詳細(xì)教程C_多線程的詳細(xì)教程Windows是一個多任務(wù)的系統(tǒng),如果你使用的是windows2000及其以上版本,你可以通過任務(wù)管理器查看當(dāng)前系統(tǒng)運行的程序和進程。

什么是進程呢?當(dāng)一個程序開始運行時,它就是一個進程,進程所指包括運行中的程序和程序所使用到的內(nèi)存和系統(tǒng)資源。

而一個進程又是由多個線程所組成的,線程是程序中的一個執(zhí)行流,每個線程都有自己的專有寄存器(棧指針、程序計數(shù)器等),但代碼區(qū)是共享的,即不同的線程可以執(zhí)行同樣的函數(shù)。

多線程是指程序中包含多個執(zhí)行流,即在一個程序中可以同時運行多個不同的線程來執(zhí)行不同的任務(wù),也就是說允許單個程序創(chuàng)建多個并行執(zhí)行的線程來完成各自的任務(wù)。

瀏覽器就是一個很好的多線程的例子,在瀏覽器中你可以在下載JAVA小應(yīng)用程序或圖象的同時滾動頁面,在訪問新頁面時,播放動畫和聲音,打印文件等。

多線程的好處在于可以提高CPU的利用率任何一個程序員都不希望自己的程序很多時候沒事可干,在多線程程序中,一個線程必須等待的時候,CPU可以運行其它的線程而不是等待,這樣就大大提高了程序的效率。

然而我們也必須認(rèn)識到線程本身可能影響系統(tǒng)性能的不利方面,以正確使用線程:

線程也是程序,所以線程需要占用內(nèi)存,線程越多占用內(nèi)存也越多多線程需要協(xié)調(diào)和管理,所以需要CPU時間跟蹤線程線程之間對共享資源的訪問會相互影響,必須解決競用共享資源的問題線程太多會導(dǎo)致控制太復(fù)雜,最終可能造成很多Bug基于以上認(rèn)識,我們可以一個比喻來加深理解。

假設(shè)有一個公司,公司里有很多各司其職的職員,那么我們可以認(rèn)為這個正常運作的公司就是一個進程,而公司里的職員就是線程。

一個公司至少得有一個職員吧,同理,一個進程至少包含一個線程。

在公司里,你可以一個職員干所有的事,但是效率很顯然是高不起來的,一個人的公司也不可能做大;一個程序中也可以只用一個線程去做事,事實上,一些過時的語言如fortune,basic都是如此,但是象一個人的公司一樣,效率很低,如果做大程序,效率更低事實上現(xiàn)在幾乎沒有單線程的商業(yè)軟件。

公司的職員越多,老板就得發(fā)越多的薪水給他們,還得耗費大量精力去管理他們,協(xié)調(diào)他們之間的矛盾和利益;程序也是如此,線程越多耗費的資源也越多,需要CPU時間去跟蹤線程,還得解決諸如死鎖,同步等問題。

總之,如果你不想你的公司被稱為皮包公司,你就得多幾個員工;如果你不想讓你的程序顯得稚氣,就在你的程序里引入多線程吧!本文將對C#編程中的多線程機制進行探討,通過一些實例解決對線程的控制,多線程間通訊等問題。

為了省去創(chuàng)建GUI那些繁瑣的步驟,更清晰地逼近線程的本質(zhì),下面所有的程序都是控制臺程序,程序最后的Console.ReadLine()是為了使程序中途停下來,以便看清楚執(zhí)行過程中的輸出。

好了,廢話少說,讓我們來體驗一下多線程的C#吧!二.操縱一個線程任何程序在執(zhí)行時,至少有一個主線程,下面這段小程序可以給讀者一個直觀的印象:

//SystemThread.csusingSystem;usingSystem.Threading;namespaceThreadTest{classRunIt{[STAThread]staticvoidMain(string[]args){Thread.CurrentThread.Name=SystemThread;//給當(dāng)前線程起名為SystemThreadConsole.WriteLine(Thread.CurrentThread.Name+’Status:+Thread.CurrentThread.ThreadState);Console.ReadLine();}}}編譯執(zhí)行后你看到了什么?是的,程序?qū)a(chǎn)生如下輸出:

SystemThread’sStatus:Running在這里,我們通過Thread類的靜態(tài)屬性CurrentThread獲取了當(dāng)前執(zhí)行的線程,對其Name屬性賦值SystemThread,最后還輸出了它的當(dāng)前狀態(tài)(ThreadState)。

所謂靜態(tài)屬性,就是這個類所有對象所公有的屬性,不管你創(chuàng)建了多少個這個類的實例,但是類的靜態(tài)屬性在內(nèi)存中只有一個。

很容易理解CurrentThread為什么是靜態(tài)的雖然有多個線程同時存在,但是在某一個時刻,CPU只能執(zhí)行其中一個。

就像上面程序所演示的,我們通過Thread類來創(chuàng)建和控制線程。

注意到程序的頭部,我們使用了如下命名空間:

usingSystem;usingSystem.Threading;在.netframeworkclasslibrary中,所有與多線程機制應(yīng)用相關(guān)的類都是放在System.Threading命名空間中的。

其中提供Thread類用于創(chuàng)建線程,ThreadPool類用于管理線程池等等,此外還提供解決了線程執(zhí)行安排,死鎖,線程間通訊等實際問題的機制。

如果你想在你的應(yīng)用程序中使用多線程,就必須包含這個類。

Thread類有幾個至關(guān)重要的方法,描述如下:

Start():啟動線程Sleep(int):靜態(tài)方法,暫停當(dāng)前線程指定的毫秒數(shù)Abort():通常使用該方法來終止一個線程Suspend():

該方法并不終止未完成的線程,它僅僅掛起線程,以后還可恢復(fù)。

Resume():恢復(fù)被Suspend()方法掛起的線程的執(zhí)行下面我們就動手來創(chuàng)建一個線程,使用Thread類創(chuàng)建線程時,只需提供線程入口即可。

線程入口使程序知道該讓這個線程干什么事,在C#中,線程入口是通過ThreadStart代理(delegate)來提供的,你可以把ThreadStart理解為一個函數(shù)指針,指向線程要執(zhí)行的函數(shù),當(dāng)調(diào)用Thread.Start()方法后,線程就開始執(zhí)行ThreadStart所代表或者說指向的函數(shù)。

打開你的VS.net,新建一個控制臺應(yīng)用程序(ConsoleApplication),下面這些代碼將讓你體味到完全控制一個線程的無窮樂趣!//ThreadTest.csusingSystem;usingSystem.Threading;namespaceThreadTest{publicclassAlpha{publicvoidBeta(){while(true){Console.WriteLine(Alpha.Betaisrunninginitsownthread.);}}};publicclassSimple{publicstaticintMain(){Console.WriteLine(ThreadStart/Stop/JoinSample);AlphaoAlpha=newAlpha();file://這里創(chuàng)建一個線程,使之執(zhí)行Alpha類的Beta()方法ThreadoThread=newThread(newThreadStart(oAlpha.Beta));oThread.Start();while(!oThread.IsAlive);Thread.Sleep(1);oThread.Abort();oThread.Join();Console.WriteLine();Console.WriteLine(Alpha.Betahasfinished);try{Console.WriteLine(TrytorestarttheAlpha.Betathread);oThread.Start();}catch(ThreadStateException){Console.Write(ThreadStateExceptiontryingtorestartAlpha.Beta.);Console.WriteLine(Expectedsinceabortedthreadscannotberestarted.);Console.ReadLine();}return0;}}}這段程序包含兩個類Alpha和Simple,在創(chuàng)建線程oThread時我們用指向Alpha.Beta()方法的初始化了ThreadStart代理(delegate)對象,當(dāng)我們創(chuàng)建的線程oThread調(diào)用oThread.Start()方法啟動時,實際上程序運行的是Alpha.Beta()方法:

AlphaoAlpha=newAlpha();ThreadoThread=newThread(newThreadStart(oAlpha.Beta));oThread.Start();然后在Main()函數(shù)的while循環(huán)中,我們使用靜態(tài)方法Thread.Sleep()讓主線程停了1ms,這段時間CPU轉(zhuǎn)向執(zhí)行線程oThread。

然后我們試圖用Thread.Abort()方法終止線程oThread,注意后面的oThread.Join(),Thread.Join()方法使主線程等待,直到oThread線程結(jié)束。

你可以給Thread.Join()方法指定一個int型的參數(shù)作為等待的最長時間。

之后,我們試圖用Thread.Start()方法重新啟動線程oThread,但是顯然Abort()方法帶來的后果是不可恢復(fù)的終止線程,所以最后程序會拋出ThreadStateException異常。

程序最后得到的結(jié)果將如下圖:

在這里我們要注意的是其它線程都是依附于Main()函數(shù)所在的線程的,Main()函數(shù)是C#程序的入口,起始線程可以稱之為主線程,如果所有的前臺線程都停止了,那么主線程可以終止,而所有的后臺線程都將無條件終止。

而所有的線程雖然在微觀上是串行執(zhí)行的,但是在宏觀上你完全可以認(rèn)為它們在并行執(zhí)行。

#endregion讀者一定注意到了Thread.ThreadState這個屬性,這個屬性代表了線程運行時狀態(tài),在不同的情況下有不同的值,于是我們有時候可以通過對該值的判斷來設(shè)計程序流程。

ThreadState在各種情況下的可能取值如下:

Aborted:

線程已停止AbortRequested:

線程的Thread.Abort()方法已被調(diào)用,但是線程還未停止Background:

線程在后臺執(zhí)行,與屬性Thread.IsBackground有關(guān)Running:

線程正在正常運行Stopped:

線程已經(jīng)被停止StopRequested:

線程正在被要求停止Suspended:

線程已經(jīng)被掛起(此狀態(tài)下,可以通過調(diào)用Resume()方法重新運行)SuspendRequested:

線程正在要求被掛起,但是未來得及響應(yīng)Unstarted:

未調(diào)用Thread.Start()開始線程的運行WaitSleepJoin:

線程因為調(diào)用了Wait(),Sleep()或Join()等方法處于封鎖狀態(tài)上面提到了Background狀態(tài)表示該線程在后臺運行,那么后臺運行的線程有什么特別的地方呢?其實后臺線程跟前臺線程只有一個區(qū)別,那就是后臺線程不妨礙程序的終止。

一旦一個進程所有的前臺線程都終止后,CLR(通用語言運行環(huán)境)將通過調(diào)用任意一個存活中的后臺進程的Abort()方法來徹底終止進程。

當(dāng)線程之間爭奪CPU時間時,CPU按照是線程的優(yōu)先級給予服務(wù)的。

在C#應(yīng)用程序中,用戶可以設(shè)定5個不同的優(yōu)先級,由高到低分別是Highest,AboveNormal,Normal,BelowNormal,Lowest,在創(chuàng)建線程時如果不指定優(yōu)先級,那么系統(tǒng)默認(rèn)為ThreadPriority.Normal。

給一個線程指定優(yōu)先級,我們可以使用如下代碼:

//設(shè)定優(yōu)先級為最低myThread.Priority=ThreadPriority.Lowest;通過設(shè)定線程的優(yōu)先級,我們可以安排一些相對重要的線程優(yōu)先執(zhí)行,例如對用戶的響應(yīng)等等。

現(xiàn)在我們對怎樣創(chuàng)建和控制一個線程已經(jīng)有了一個初步的了解,下面我們將深入研究線程實現(xiàn)中比較典型的的問題,并且探討其解決方法。

三.線程的同步和通訊生產(chǎn)者和消費者假設(shè)這樣一種情況,兩個線程同時維護一個隊列,如果一個線程對隊列中添加元素,而另外一個線程從隊列中取用元素,那么我們稱添加元素的線程為生產(chǎn)者,稱取用元素的線程為消費者。

生產(chǎn)者與消費者問題看起來很簡單,但是卻是多線程應(yīng)用中一個必須解決的問題,它涉及到線程之間的同步和通訊問題。

前面說過,每個線程都有自己的資源,但是代碼區(qū)是共享的,即每個線程都可以執(zhí)行相同的函數(shù)。

但是多線程環(huán)境下,可能帶來的問題就是幾個線程同時執(zhí)行一個函數(shù),導(dǎo)致數(shù)據(jù)的混亂,產(chǎn)生不可預(yù)料的結(jié)果,因此我們必須避免這種情況的發(fā)生。

C#提供了一個關(guān)鍵字lock,它可以把一段代碼定義為互斥段(criticalsection),互斥段在一個時刻內(nèi)只允許一個線程進入執(zhí)行,而其他線程必須等待。

在C#中,關(guān)鍵字lock定義如下:

lock(expression)statement_blockexpression代表你希望跟蹤的對象,通常是對象引用。

一般地,如果你想保護一個類的實例,你可以使用this;如果你希望保護一個靜態(tài)變量(如互斥代碼段在一個靜態(tài)方法內(nèi)部),一般使用類名就可以了。

而statement_block就是互斥段的代碼,這段代碼在一個時刻內(nèi)只可能被一個線程執(zhí)行。

下面是一個使用lock關(guān)鍵字的典型例子,我將在注釋里向大家說明lock關(guān)鍵字的用法和用途:

//lock.csusingSystem;usingSystem.Threading;internalclassAccount{intbalance;Randomr=newRandom();internalAccount(intinitial){balance=initial;}internalintWithdraw(intamount){if(balance0){file://如果balance小于0則拋出異常thrownewException(NegativeBalance);}//下面的代碼保證在當(dāng)前線程修改balance的值完成之前//不會有其他線程也執(zhí)行這段代碼來修改balance的值//因此,balance的值是不可能小于0的lock(this){Console.WriteLine(CurrentThread:+Thread.CurrentThread.Name);file://如果沒有l(wèi)ock關(guān)鍵字的保護,那么可能在執(zhí)行完if的條件判斷之后file://另外一個線程卻執(zhí)行了balance=balance-amount修改了balance的值file://而這個修改對這個線程是不可見的,所以可能導(dǎo)致這時if的條件已經(jīng)不成立了file://但是,這個線程卻繼續(xù)執(zhí)行balance=balance-amount,所以導(dǎo)致balance可能小于0if(balance=amount){Thread.Sleep(5);balance=balance-amount;returnamount;}else{return0;//transactionrejected}}}internalvoidDoTransactions(){for(inti=0;i100;i++)Withdraw(r.Next(-50,100));}}internalclassTest{staticinternalThread[]threads=newThread[10];publicstaticvoidMain(){Accountacc=newAccount(0);for(inti=0;i10;i++){Threadt=newThread(newThreadStart(acc.DoTransactions));threads[i]=t;}for(inti=0;i10;i++)threads[i].Name=i.ToString();for(inti=0;i10;i++)threads[i].Start();Console.ReadLine();}}而多線程公用一個對象時,也會出現(xiàn)和公用代碼類似的問題,這種問題就不應(yīng)該使用lock關(guān)鍵字了,這里需要用到System.Threading中的一個類Monitor,我們可以稱之為監(jiān)視器,Monitor提供了使線程共享資源的方案。

Monitor類可以鎖定一個對象,一個線程只有得到這把鎖才可以對該對象進行操作。

對象鎖機制保證了在可能引起混亂的情況下一個時刻只有一個線程可以訪問這個對象。

Monitor必須和一個具體的對象相關(guān)聯(lián),但是由于它是一個靜態(tài)的類,所以不能使用它來定義對象,而且它的所有方法都是靜態(tài)的,不能使用對象來引用。

下面代碼說明了使用Monitor鎖定一個對象的情形:

QueueoQueue=newQueue();Monitor.Enter(oQueue);//現(xiàn)在oQueue對象只能被當(dāng)前線程操縱了Monitor.Exit(oQueue);//釋放鎖如上所示,當(dāng)一個線程調(diào)用Monitor.Enter()方法鎖定一個對象時,這個對象就歸它所有了,其它線程想要訪問這個對象,只有等待它使用

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論