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

下載本文檔

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

文檔簡(jiǎn)介

第9章

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

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

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

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

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

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

staticvoidDisplayNumbers() { //獲取當(dāng)前運(yùn)行線程的Thread對(duì)象實(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ù)與終止本示例通過(guò)啟動(dòng)第二個(gè)工作線程來(lái)運(yùn)行DisplayNumbers(),但啟動(dòng)這個(gè)工作線程后,主線程就開(kāi)始執(zhí)行同一個(gè)方法,此時(shí)我們應(yīng)看到有兩個(gè)累加過(guò)程同時(shí)發(fā)生。以下給出本示例的全部代碼。該代碼段從類的聲明開(kāi)始,interval是這個(gè)類的一個(gè)靜態(tài)成員。9.2.2線程的掛起、恢復(fù)與終止兩個(gè)累加過(guò)程是完全獨(dú)立的,因?yàn)镈isplayNumbers()方法中用于累加數(shù)字的變量i是一個(gè)局部變量。局部變量只能在定義它們的方法中使用,也只有在執(zhí)行該方法的線程中是可見(jiàn)的。如果另一個(gè)線程開(kāi)始執(zhí)行這個(gè)方法,該線程就會(huì)獲得該局部變量的副本。運(yùn)行這段代碼,給interval選擇一個(gè)相對(duì)小的值100,得到如圖9.1所示的結(jié)果。9.2.2線程的掛起、恢復(fù)與終止為了使線程的并行看得更為明顯,我們?cè)谳斎霐?shù)字的時(shí)候輸入一個(gè)較大的值1000000,從而使得循環(huán)的時(shí)間大大加長(zhǎng),在主線程結(jié)束之前工作線程也開(kāi)始工作了?,F(xiàn)在來(lái)看運(yùn)行結(jié)果如圖9.2所示(由于不同的計(jì)算機(jī)運(yùn)行速度不同,下面的結(jié)果可能略有不同)。9.2.3線程的狀態(tài)及優(yōu)先級(jí)注意到Thread.ThreadState這個(gè)屬性,它代表了線程運(yùn)行時(shí)的狀態(tài),在不同的情況下有不同的值,有時(shí)通過(guò)對(duì)該值的判斷來(lái)設(shè)計(jì)程序流程。ThreadState在各種情況下的可能取值如表9.3所示。線程狀態(tài)說(shuō)明Aborted線程已停止AbortRequested線程的Thread.Abort()方法已被調(diào)用,但是線程還未停止Background線程在后臺(tái)執(zhí)行,與屬性Thread.IsBackground有關(guān)Running線程正常運(yùn)行Stopped線程已被停止StopRequested線程正在被要求停止Suspended線程已被掛起(此狀態(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)。9.2.3線程的狀態(tài)及優(yōu)先級(jí)線程的優(yōu)先級(jí)定義為T(mén)hreadPriority枚舉類型,取值如表9.4所示。名稱含義Highest將線程安排在具有任何其他優(yōu)先級(jí)的線程之前。AboveNormal將線程安排在具有

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

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

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

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

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

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

Lowest優(yōu)先級(jí)的線程之前。Lowest將線程安排在具有任何其他優(yōu)先級(jí)的線程之后。9.2.3線程的狀態(tài)及優(yōu)先級(jí)在創(chuàng)建線程時(shí)如果不指定優(yōu)先級(jí),系統(tǒng)將默認(rèn)為T(mén)hreadPriority.Normal。給一個(gè)線程指定優(yōu)先級(jí),可以使用如下代碼: myThread.Priority=ThreadPriority.Lowest; //設(shè)定優(yōu)先級(jí)為最低通過(guò)設(shè)定線程的優(yōu)先級(jí),可以安排一些相對(duì)重要的線程優(yōu)先執(zhí)行,如對(duì)用戶的響應(yīng)等?!纠?.2】在【例9.1】中,對(duì)Main()方法做如下修改,就可以看出修改線程的優(yōu)先級(jí)的效果://建立新線程對(duì)象ThreadStartworkerStart=newThreadStart(DisplayNumbers);ThreadworkerThread=newThread(workerStart);workerThread.Name=ThreadPriority.AboveNormal;workerThread.Priority=AboveNormal;9.2.3線程的狀態(tài)及優(yōu)先級(jí)其中通過(guò)代碼設(shè)置工作線程的優(yōu)先級(jí)比主線程高,運(yùn)行結(jié)果如圖9.3所示。9.2.3線程的狀態(tài)及優(yōu)先級(jí)這說(shuō)明,當(dāng)工作線程的優(yōu)先級(jí)為AboveNormal時(shí),一旦工作線程被啟動(dòng),主線程就不再運(yùn)行,直到工作線程結(jié)束后主線程才重新計(jì)算。讓我們繼續(xù)試驗(yàn)操作系統(tǒng)如何對(duì)線程分配CPU時(shí)間:在DisplayNumbers()方法的循環(huán)體中加上一句代碼(加黑語(yǔ)句): 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)先級(jí)現(xiàn)在來(lái)看運(yùn)行結(jié)果如圖9.4所示。9.3線程的同步和通信9.3.1lock關(guān)鍵字C#提供了一個(gè)關(guān)鍵字lock,它可以把一段代碼定義為互斥段?;コ舛卧谝粋€(gè)時(shí)刻只允許一個(gè)線程進(jìn)入執(zhí)行,而其他線程必須等待。在C#中,關(guān)鍵字lock定義如下: lock(expression) { statement_block //將要執(zhí)行的代碼 }expression代表希望跟蹤的對(duì)象,通常是對(duì)象引用。9.3.1LOCK關(guān)鍵字【例9.3】設(shè)計(jì)控制臺(tái)應(yīng)用程序來(lái)體現(xiàn)lock關(guān)鍵字的使用。設(shè)計(jì)步驟:(1)新建控制臺(tái)應(yīng)用程序新建控制臺(tái)應(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對(duì)象,開(kāi)始位置為200for(inti=0;i<10;i++) //實(shí)例化10個(gè)線程{Threadt=newThread(newThreadStart(acc.DoTransactions));threads[i]=t;}for(inti=0;i<10;i++) //開(kāi)啟這10個(gè)線程{ threads[i].Start(); }}}9.3.1LOCK關(guān)鍵字(5)運(yùn)行程序運(yùn)行程序,程序的運(yùn)行結(jié)果如圖9.5所示。9.3.2線程監(jiān)視器多線程公用一個(gè)對(duì)象時(shí),也會(huì)出現(xiàn)和公用代碼類似的問(wèn)題,這種問(wèn)題就不應(yīng)使用lock關(guān)鍵字,而要用到System.Threading中的一個(gè)類Monitor,稱為監(jiān)視器,Monitor提供了使線程共享資源的方案。Monitor的常用方法如表9.5所示。線程狀態(tài)說(shuō)明Enter在指定對(duì)象上獲取排他鎖。Exit釋放指定對(duì)象上的排他鎖。Pulse通知等待隊(duì)列中的線程鎖定對(duì)象狀態(tài)的更改。PulseAll通知所有的等待線程對(duì)象狀態(tài)的更改。TryEnter試圖獲取指定對(duì)象的排他鎖。

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

...... //方法 { Queuequeue=newQueue(); //新建對(duì)象queue Monitor.Enter(queue); try { //...... //現(xiàn)在Queue對(duì)象只能被當(dāng)前線程操縱了 } finally { Monitor.Exit(queue); //釋放鎖 }}9.3.3線程間的通信當(dāng)有若干個(gè)線程都要使用某一共享資源時(shí),任何時(shí)刻最多只允許一個(gè)線程去使用,其它要使用該資源的線程必須等待,直到占用資源者釋放該資源。線程互斥是一種特殊的線程同步。實(shí)際上,互斥和同步對(duì)應(yīng)著線程間通信發(fā)生的兩種情況:(1)當(dāng)有多個(gè)線程訪問(wèn)共享資源而不使資源被破壞時(shí)。(2)當(dāng)一個(gè)線程需要將某個(gè)任務(wù)已經(jīng)完成的情況通知另外一個(gè)或多個(gè)線程時(shí)。9.3.3線程間的通信【例9.4】線程間的通信。設(shè)計(jì)步驟:(1)新建控制臺(tái)應(yīng)用程序新建控制臺(tái)應(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子線程訪問(wèn)主線程的控件【例9.5】設(shè)計(jì)WinForm應(yīng)用程序來(lái)利用子線程訪問(wèn)主線程創(chuàng)建的控件。設(shè)計(jì)步驟:(1)新建WinForm應(yīng)用程序新建WinForm應(yīng)用程序并命名為“Ex9_5”。(2)設(shè)計(jì)窗體并添加控件將窗體調(diào)整到適當(dāng)大小,拖放一個(gè)TrackBar和一個(gè)Button控件。Form1的Text設(shè)置為“子線程訪問(wèn)主線程控件”,trackBar1的Maximum和LargeChange分別設(shè)置為“100”和“1”。(3)添加命名空間切換到代碼設(shè)計(jì)視圖。因?yàn)樯婕暗骄€程操作,所以添加命名空間: usingSystem.Threading;(4)添加事件和代碼切換到設(shè)計(jì)視圖。雙擊button1控件,添加代碼。9.3.4子線程訪問(wèn)主線程的控件(5)運(yùn)行程序運(yùn)行程序。單擊“button1”按鈕,運(yùn)行結(jié)果如圖9.7所示。9.4線程池和定時(shí)器9.

溫馨提示

  • 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ì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論