敬阿奇,C程序設(shè)計(jì)教程(第3版),第9章_第1頁
敬阿奇,C程序設(shè)計(jì)教程(第3版),第9章_第2頁
敬阿奇,C程序設(shè)計(jì)教程(第3版),第9章_第3頁
敬阿奇,C程序設(shè)計(jì)教程(第3版),第9章_第4頁
敬阿奇,C程序設(shè)計(jì)教程(第3版),第9章_第5頁
已閱讀5頁,還剩31頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

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

文檔簡介

第9章

多線程編程9.1線程概述9.2創(chuàng)建并控制一個線程9.3線程的同步和通信9.4線程池和定時器9.5互斥對象9.1線程概述瀏覽器就是一個很好的多線程例子,在瀏覽器中可以在下載Java小應(yīng)用程序或圖像的同時滾動頁面,在訪問新頁面時播放動畫、聲音并打印文件等。多線程程序中,在一個線程必須等待的時候,CPU可以運(yùn)行其他線程而不是等待,這就大大提高了程序的效率。

然而,我們也必須認(rèn)識到線程本身可能存在影響系統(tǒng)性能的不利方面,才能正確使用線程。不利方面主要有:(1)線程也是程序,所以線程需要占用內(nèi)存,線程越多占用內(nèi)存也越多。(2)多線程需要協(xié)調(diào)和管理,所以需要占用CPU時間來跟蹤線程。(3)線程之間對共享資源的訪問會相互影響,必須解決爭用共享資源的問題。(4)線程太多會導(dǎo)致控制太復(fù)雜,最終可能造成很多Bug。9.1線程概述當(dāng)啟動一個可執(zhí)行程序時,將創(chuàng)建一個主線程,默認(rèn)情況下,C#程序具有一個線程。此線程執(zhí)行程序中以Main方法開始和結(jié)束的代碼。Main直接或間接執(zhí)行的每一個命令都由默認(rèn)線程(或主線程)執(zhí)行,當(dāng)Main返回時此線程也將終止。例如創(chuàng)建一個Windows窗體應(yīng)用程序,打開其中的“Program.cs”文件,其代碼如下:staticclassProgram{///<summary>///應(yīng)用程序的主入口點(diǎn)。///</summary>[STAThread]

staticvoidMain(){Application.EnableVisualStyles();Application.SetCompatibleTextRenderingDefault(false);Application.Run(newForm1());}}9.1.1多線程工作方式

一個處理器在某一刻只能處理一個任務(wù),對于一個多處理器系統(tǒng),理論上它可以同時執(zhí)行多個指令——每個處理器執(zhí)行一個指令,但大多數(shù)人使用的是單處理器計(jì)算機(jī),這種情況是不可能同時發(fā)生的。表面上Windows操作系統(tǒng)上可以同時處理多個任務(wù),這個過程稱為搶先式多任務(wù)處理(pre-emptivemultitasking),所謂搶先式多任務(wù)處理,是指Windows在某個進(jìn)程中選擇—個線程,該線程運(yùn)行一小段時間。這個時間非常短,不會超過幾毫秒。這段很短的時間稱為線程的時間片(timeslice)。過了這個時間片后,Windows就收回控制權(quán),選擇下一個被分配了時間片的線程。這些時間片非常短,我們可以認(rèn)為許多事件是同時發(fā)生的。

9.1.2什么時候使用多線程應(yīng)用多線程技術(shù)最大的誤區(qū)在于沒有分清適用的情況就盲目地使用多線程。除非運(yùn)行一個多處理器計(jì)算機(jī),否則在CPU密集的任務(wù)中使用兩個線程不能節(jié)省多少時間,理解這一點(diǎn)是很重要的。在單處理器計(jì)算機(jī)上,讓兩個線程都同時進(jìn)行100萬次運(yùn)算所花的時間與讓一個線程進(jìn)行200萬次運(yùn)算是相同的,甚至使用兩個線程所用的時間會略長,因?yàn)橐幚砹硪粋€線程,操作系統(tǒng)必須用一定的時間切換線程,但這種區(qū)別可以忽略不計(jì)。使用線程帶來的負(fù)面因素是必須額外考慮線程的并發(fā)、同步等線程安全問題,從而使得程序更加復(fù)雜而難以維護(hù)。有些場合則使用多線程技術(shù)非常適合,如一個服務(wù)器進(jìn)程需要并發(fā)處理來自不同客戶端的訪問。此外,使用多個線程的優(yōu)點(diǎn)有兩個。9.2創(chuàng)建并控制一個線程9.2.1線程的建立與啟動通過實(shí)例化一個Thread對象就可以創(chuàng)建一個線程。創(chuàng)建新的Thread對象時,將創(chuàng)建新的托管線程。Thread類接收一個ThreadStart委托或ParameterizedThreadStart委托的構(gòu)造函數(shù),該委托包裝了調(diào)用Start方法時由新線程調(diào)用的方法。示例代碼如下: Threadthread=newThread(newThreadStart(methord)); //創(chuàng)建線程 thread.Start(); //啟動線程上述代碼實(shí)例化了一個Thread對象,并指明了將要調(diào)用的方法methord,然后啟動線程。ThreadStart委托中作為參數(shù)的方法不需要參數(shù),并且沒有返回值。ParameterizedThreadStart委托一個對象為參數(shù),利用這個參數(shù)可以很方便地向線程傳遞參數(shù)。示例代碼如下: Threadthread=newThread(newParameterizedThreadStart(methord)); //創(chuàng)建線程 thread.Start(3); //啟動線程并傳參數(shù)3Thread類的常用屬性和方法如表9.1和表9.2所示。9.2.2線程的掛起、恢復(fù)與終止線程通過調(diào)用Suspend方法來掛起線程。當(dāng)線程針對自身調(diào)用Suspend方法時,調(diào)用將會阻止,直到另一個線程繼續(xù)該線程。當(dāng)一個線程針對另一個線程調(diào)用Suspend方法時,調(diào)用是非阻止調(diào)用,這會導(dǎo)致另一線程掛起。線程通過調(diào)用Resume方法來恢復(fù)被掛起的線程。無論調(diào)用了多少次Suspend方法,調(diào)用Resume方法均會使另一個線程脫離掛起狀態(tài),并導(dǎo)致該線程繼續(xù)執(zhí)行。示例代碼如下:

Threadthread=newThread(newThreadStart(methord)); //創(chuàng)建線程 thread.Start(); //啟動線程 thread.Suspend(); //掛起線程 thread..Resume(); //恢復(fù)線程9.2.2線程的掛起、恢復(fù)與終止如果在應(yīng)用程序中使用了多線程,輔助線程還沒有執(zhí)行完畢,在關(guān)閉窗體的時候必須要關(guān)閉輔助線程,否則會引發(fā)異常。示例代碼如下: Threadthread=newThread(newThreadStart(methord)); //創(chuàng)建線程 thread.Start(); //啟動線程 if(thread.IsAlive) { thread.Abort; } //關(guān)閉線程9.2.2線程的掛起、恢復(fù)與終止.NET使用的異常機(jī)制使線程的中止更加安全,但中止線程要用一定的時間,因?yàn)閺睦碚撋现v,異常處理塊中的代碼執(zhí)行多長時間是沒有限制的。因此,在中止線程后需要等待一段時間,線程完全中止后,才能繼續(xù)執(zhí)行其他操作。如果后續(xù)的處理依賴于該中止的線程,可以使用Join()方法,等待線程中止: thread.Abort(); thread.Join();通過使用Join(),線程可以在中止前阻塞調(diào)用它的代碼。如果主線程要在它自己的線程上執(zhí)行某些操作,該怎么辦?此時需要一個線程對象的引用來表示它自己的線程。在主線程中使用Thread類的靜態(tài)屬性CurrentThread,就可以獲得這樣一個引用: ThreadmyOwnThread=Thread.CurrentThread;9.2.2線程的掛起、恢復(fù)與終止【例9.1】使用兩個線程顯示計(jì)數(shù)。該示例的核心是方法DisplayNumbers(),它累加一個數(shù)字,并定期顯示每次累加的結(jié)果:

staticvoidDisplayNumbers() { //獲取當(dāng)前運(yùn)行線程的Thread對象實(shí)例并輸出名稱 ThreadthisThread=Thread.CurrentThread; Console.WriteLine("Startingthread:"+thisThread.Name); //循環(huán)計(jì)數(shù)直到結(jié)束,在指定的間隔輸出當(dāng)前計(jì)數(shù)值 for(inti=1;i<8*interval;i++) { if(i%interval==0) { Console.WriteLine(thisThread.Name+":當(dāng)前計(jì)數(shù)為"+i); } } Console.WriteLine("Thread"+thisThread.Name+"finished."); }9.2.2線程的掛起、恢復(fù)與終止本示例通過啟動第二個工作線程來運(yùn)行DisplayNumbers(),但啟動這個工作線程后,主線程就開始執(zhí)行同一個方法,此時我們應(yīng)看到有兩個累加過程同時發(fā)生。以下給出本示例的全部代碼。該代碼段從類的聲明開始,interval是這個類的一個靜態(tài)成員。9.2.2線程的掛起、恢復(fù)與終止兩個累加過程是完全獨(dú)立的,因?yàn)镈isplayNumbers()方法中用于累加數(shù)字的變量i是一個局部變量。局部變量只能在定義它們的方法中使用,也只有在執(zhí)行該方法的線程中是可見的。如果另一個線程開始執(zhí)行這個方法,該線程就會獲得該局部變量的副本。運(yùn)行這段代碼,給interval選擇一個相對小的值100,得到如圖9.1所示的結(jié)果。9.2.2線程的掛起、恢復(fù)與終止為了使線程的并行看得更為明顯,我們在輸入數(shù)字的時候輸入一個較大的值1000000,從而使得循環(huán)的時間大大加長,在主線程結(jié)束之前工作線程也開始工作了?,F(xiàn)在來看運(yùn)行結(jié)果如圖9.2所示(由于不同的計(jì)算機(jī)運(yùn)行速度不同,下面的結(jié)果可能略有不同)。9.2.3線程的狀態(tài)及優(yōu)先級注意到Thread.ThreadState這個屬性,它代表了線程運(yùn)行時的狀態(tài),在不同的情況下有不同的值,有時通過對該值的判斷來設(shè)計(jì)程序流程。ThreadState在各種情況下的可能取值如表9.3所示。線程狀態(tài)說明Aborted線程已停止AbortRequested線程的Thread.Abort()方法已被調(diào)用,但是線程還未停止Background線程在后臺執(zhí)行,與屬性Thread.IsBackground有關(guān)Running線程正常運(yùn)行Stopped線程已被停止StopRequested線程正在被要求停止Suspended線程已被掛起(此狀態(tài)下,可以通過調(diào)用Resume()方法重新運(yùn)行)SuspendRequested線程正在要求被掛起,但未來得及響應(yīng)Unstarted未調(diào)用Thread.Start()開始線程的運(yùn)行WaitSleepJoin線程因?yàn)檎{(diào)用了Wait()、Sleep()或Join()等方法而處于封鎖狀態(tài)。9.2.3線程的狀態(tài)及優(yōu)先級線程的優(yōu)先級定義為ThreadPriority枚舉類型,取值如表9.4所示。名稱含義Highest將線程安排在具有任何其他優(yōu)先級的線程之前。AboveNormal將線程安排在具有

Highest優(yōu)先級的線程之后,在具有

Normal優(yōu)先級的線程之前。Normal將線程安排在具有

AboveNormal優(yōu)先級的線程之后,在具有

BelowNormal優(yōu)先級的線程之前。默認(rèn)情況下,線程具有

Normal優(yōu)先級。BelowNormal將線程安排在具有

Normal優(yōu)先級的線程之后,在具有

Lowest優(yōu)先級的線程之前。Lowest將線程安排在具有任何其他優(yōu)先級的線程之后。9.2.3線程的狀態(tài)及優(yōu)先級在創(chuàng)建線程時如果不指定優(yōu)先級,系統(tǒng)將默認(rèn)為ThreadPriority.Normal。給一個線程指定優(yōu)先級,可以使用如下代碼: myThread.Priority=ThreadPriority.Lowest; //設(shè)定優(yōu)先級為最低通過設(shè)定線程的優(yōu)先級,可以安排一些相對重要的線程優(yōu)先執(zhí)行,如對用戶的響應(yīng)等。【例9.2】在【例9.1】中,對Main()方法做如下修改,就可以看出修改線程的優(yōu)先級的效果://建立新線程對象ThreadStartworkerStart=newThreadStart(DisplayNumbers);ThreadworkerThread=newThread(workerStart);workerThread.Name=ThreadPriority.AboveNormal;workerThread.Priority=AboveNormal;9.2.3線程的狀態(tài)及優(yōu)先級其中通過代碼設(shè)置工作線程的優(yōu)先級比主線程高,運(yùn)行結(jié)果如圖9.3所示。9.2.3線程的狀態(tài)及優(yōu)先級這說明,當(dāng)工作線程的優(yōu)先級為AboveNormal時,一旦工作線程被啟動,主線程就不再運(yùn)行,直到工作線程結(jié)束后主線程才重新計(jì)算。讓我們繼續(xù)試驗(yàn)操作系統(tǒng)如何對線程分配CPU時間:在DisplayNumbers()方法的循環(huán)體中加上一句代碼(加黑語句): if(i%interval==0) { Console.WriteLine(thisThread.Name+":當(dāng)前計(jì)數(shù)為"+i);

Thread.Sleep(10); //讓當(dāng)前工作線程暫停10毫秒 }9.2.3線程的狀態(tài)及優(yōu)先級現(xiàn)在來看運(yùn)行結(jié)果如圖9.4所示。9.3線程的同步和通信9.3.1lock關(guān)鍵字C#提供了一個關(guān)鍵字lock,它可以把一段代碼定義為互斥段?;コ舛卧谝粋€時刻只允許一個線程進(jìn)入執(zhí)行,而其他線程必須等待。在C#中,關(guān)鍵字lock定義如下: lock(expression) { statement_block //將要執(zhí)行的代碼 }expression代表希望跟蹤的對象,通常是對象引用。9.3.1LOCK關(guān)鍵字【例9.3】設(shè)計(jì)控制臺應(yīng)用程序來體現(xiàn)lock關(guān)鍵字的使用。設(shè)計(jì)步驟:(1)新建控制臺應(yīng)用程序新建控制臺應(yīng)用程序并命名為“Ex9_3”。(2)添加類添加類,類名為“Account”,其代碼如下所示。(3)添加命名空間所要添加的命名空間為: usingSystem.Threading;9.3.1LOCK關(guān)鍵字(4)添加Main方法中的代碼所添加的代碼如下:classProgram{staticvoidMain(string[]args){Thread[]threads=newThread[10];Accountacc=newAccount(200);//實(shí)例化Account對象,開始位置為200for(inti=0;i<10;i++) //實(shí)例化10個線程{Threadt=newThread(newThreadStart(acc.DoTransactions));threads[i]=t;}for(inti=0;i<10;i++) //開啟這10個線程{ threads[i].Start(); }}}9.3.1LOCK關(guān)鍵字(5)運(yùn)行程序運(yùn)行程序,程序的運(yùn)行結(jié)果如圖9.5所示。9.3.2線程監(jiān)視器多線程公用一個對象時,也會出現(xiàn)和公用代碼類似的問題,這種問題就不應(yīng)使用lock關(guān)鍵字,而要用到System.Threading中的一個類Monitor,稱為監(jiān)視器,Monitor提供了使線程共享資源的方案。Monitor的常用方法如表9.5所示。線程狀態(tài)說明Enter在指定對象上獲取排他鎖。Exit釋放指定對象上的排他鎖。Pulse通知等待隊(duì)列中的線程鎖定對象狀態(tài)的更改。PulseAll通知所有的等待線程對象狀態(tài)的更改。TryEnter試圖獲取指定對象的排他鎖。

Wait釋放對象上的鎖并阻止當(dāng)前線程,直到它重新獲取該鎖。9.3.2線程監(jiān)視器下面的代碼說明了使用Monitor鎖定一個對象queue的情形:

...... //方法 { Queuequeue=newQueue(); //新建對象queue Monitor.Enter(queue); try { //...... //現(xiàn)在Queue對象只能被當(dāng)前線程操縱了 } finally { Monitor.Exit(queue); //釋放鎖 }}9.3.3線程間的通信當(dāng)有若干個線程都要使用某一共享資源時,任何時刻最多只允許一個線程去使用,其它要使用該資源的線程必須等待,直到占用資源者釋放該資源。線程互斥是一種特殊的線程同步。實(shí)際上,互斥和同步對應(yīng)著線程間通信發(fā)生的兩種情況:(1)當(dāng)有多個線程訪問共享資源而不使資源被破壞時。(2)當(dāng)一個線程需要將某個任務(wù)已經(jīng)完成的情況通知另外一個或多個線程時。9.3.3線程間的通信【例9.4】線程間的通信。設(shè)計(jì)步驟:(1)新建控制臺應(yīng)用程序新建控制臺應(yīng)用程序并命名為“Ex9_4”。(2)添加命名空間切換到代碼設(shè)計(jì)視圖。因?yàn)樯婕暗骄€程操作,所以添加命名空間:usingSystem.Threading;(3)添加類添加學(xué)生類“Student”。9.3.3線程間的通信添加線程1類,主要用于添加學(xué)生信息。publicclassThread1{privateStudentstudent;publicThread1(Studentstudent){ this.student=student; }publicvoidrun(){inti=0;while(true){i++;student.Add("學(xué)生"+i.ToString(),"1511"+i.ToString());}}}9.3.3線程間的通信添加線程2類,主要用于獲取學(xué)生信息。publicclassThread2{privateStudentstudent;publicThread2(Studentstudent){ this.student=student; }publicvoidrun(){while(true){ student.GetInfo(); }}}9.3.3線程間的通信(4)添加Main方法中的代碼所添加的代碼如下:classProgram{staticvoidMain(string[]args){Studentstudent=newStudent(); //實(shí)例化學(xué)生類newThread(newThreadStart(newThread1(student).run)).Start();//添加學(xué)生信息newThread(newThreadStart(newThread2(student).run)).Start();//讀取學(xué)生信息}}9.3.3線程間的通信(5)運(yùn)行程序運(yùn)行程序,程序的運(yùn)行結(jié)果如圖9.6所示。9.3.4子線程訪問主線程的控件【例9.5】設(shè)計(jì)WinForm應(yīng)用程序來利用子線程訪問主線程創(chuàng)建的控件。設(shè)計(jì)步驟:(1)新建WinForm應(yīng)用程序新建WinForm應(yīng)用程序并命名為“Ex9_5”。(2)設(shè)計(jì)窗體并添加控件將窗體調(diào)整到適當(dāng)大小,拖放一個TrackBar和一個Button控件。Form1的Text設(shè)置為“子線程訪問主線程控件”,trackBar1的Maximum和LargeChange分別設(shè)置為“100”和“1”。(3)添加命名空間切換到代碼設(shè)計(jì)視圖。因?yàn)樯婕暗骄€程操作,所以添加命名空間: usingSystem.Threading;(4)添加事件和代碼切換到設(shè)計(jì)視圖。雙擊button1控件,添加代碼。9.3.4子線程訪問主線程的控件(5)運(yùn)行程序運(yùn)行程序。單擊“button1”按鈕,運(yùn)行結(jié)果如圖9.7所示。9.4線程池和定時器9.

溫馨提示

  • 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)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論