線(xiàn)程 被定義為程序的執(zhí)行路徑。每個(gè)線(xiàn)程都定義了一個(gè)獨(dú)特的控制流,。如果您的應(yīng)用程序涉及到復(fù)雜的和耗時(shí)的操作,,那么設(shè)置不同的線(xiàn)程執(zhí)行路徑往往是有益的,每個(gè)線(xiàn)程執(zhí)行特定的工作,。 線(xiàn)程生命周期線(xiàn)程生命周期開(kāi)始于 System.Threading.Thread 類(lèi)的對(duì)象被創(chuàng)建時(shí),,結(jié)束于線(xiàn)程被終止或完成執(zhí)行時(shí)。 下面列出了線(xiàn)程生命周期中的各種狀態(tài):
主線(xiàn)程進(jìn)程中第一個(gè)被執(zhí)行的線(xiàn)程稱(chēng)為主線(xiàn)程,。 當(dāng) C# 程序開(kāi)始執(zhí)行時(shí),,主線(xiàn)程自動(dòng)創(chuàng)建。使用 Thread 類(lèi)創(chuàng)建的線(xiàn)程被主線(xiàn)程的子線(xiàn)程調(diào)用,。您可以使用 Thread 類(lèi)的 CurrentThread 屬性訪(fǎng)問(wèn)線(xiàn)程,。 下面的程序演示了主線(xiàn)程的執(zhí)行: 實(shí)例using
System
; using System.Threading ; namespace MultithreadingApplication { class MainThreadProgram { static void Main ( string [ ] args ) { Thread th = Thread . CurrentThread ; th . Name = 'MainThread' ; Console . WriteLine ( 'This is {0}', th . Name ) ; Console . ReadKey ( ) ; } } } 當(dāng)上面的代碼被編譯和執(zhí)行時(shí),它會(huì)產(chǎn)生下列結(jié)果: This is MainThread 創(chuàng)建線(xiàn)程線(xiàn)程是通過(guò)Thread 類(lèi)創(chuàng)建的,。Thread 類(lèi)調(diào)用 Start() 方法來(lái)開(kāi)始子線(xiàn)程的執(zhí)行,。 下面的程序演示了這個(gè)概念: using System;using System.Threading;namespace MultithreadingApplication{ class ThreadCreationProgram { public static void CallToChildThread() { Console.WriteLine('Child thread starts'); } static void Main(string[] args) { ThreadStart childref = new ThreadStart(CallToChildThread); Console.WriteLine('In Main: Creating the Child thread'); Thread childThread = new Thread(childref); childThread.Start(); Console.ReadKey(); } }} 當(dāng)上面的代碼被編譯和執(zhí)行時(shí),它會(huì)產(chǎn)生下列結(jié)果: In Main: Creating the Child threadChild thread starts 管理線(xiàn)程Thread 類(lèi)提供了各種管理線(xiàn)程的方法,。 下面的實(shí)例演示了 sleep() 方法的使用,,用于在一個(gè)特定的時(shí)間暫停線(xiàn)程。 using System;using System.Threading;namespace MultithreadingApplication{ class ThreadCreationProgram { public static void CallToChildThread() { Console.WriteLine('Child thread starts'); // 線(xiàn)程暫停 5000 毫秒 int sleepfor = 5000; Console.WriteLine('Child Thread Paused for {0} seconds', sleepfor / 1000); Thread.Sleep(sleepfor); Console.WriteLine('Child thread resumes'); } static void Main(string[] args) { ThreadStart childref = new ThreadStart(CallToChildThread); Console.WriteLine('In Main: Creating the Child thread'); Thread childThread = new Thread(childref); childThread.Start(); Console.ReadKey(); } }} 當(dāng)上面的代碼被編譯和執(zhí)行時(shí),,它會(huì)產(chǎn)生下列結(jié)果: In Main: Creating the Child threadChild thread startsChild Thread Paused for 5 secondsChild thread resumes 銷(xiāo)毀線(xiàn)程Abort() 方法用于銷(xiāo)毀線(xiàn)程,。 通過(guò)拋出 threadabortexception 在運(yùn)行時(shí)中止線(xiàn)程。這個(gè)異常不能被捕獲,,如果有 finally 塊,,控制會(huì)被送至 finally 塊。 這個(gè)異常不能被捕獲是什么鬼,可以被捕獲呀 下面的程序說(shuō)明了這點(diǎn): using System;using System.Threading;namespace MultithreadingApplication{ class ThreadCreationProgram { public static void CallToChildThread() { try { Console.WriteLine('Child thread starts'); // 計(jì)數(shù)到 10 for (int counter = 0; counter <= 10; counter++) { Thread.Sleep(500); Console.WriteLine(counter); } Console.WriteLine('Child Thread Completed'); } catch (ThreadAbortException e) { Console.WriteLine('Thread Abort Exception'); } finally { Console.WriteLine('Couldn't catch the Thread Exception'); } } static void Main(string[] args) { ThreadStart childref = new ThreadStart(CallToChildThread); Console.WriteLine('In Main: Creating the Child thread'); Thread childThread = new Thread(childref); childThread.Start(); // 停止主線(xiàn)程一段時(shí)間 Thread.Sleep(2000); // 現(xiàn)在中止子線(xiàn)程 Console.WriteLine('In Main: Aborting the Child thread'); childThread.Abort(); Console.ReadKey(); } }} 當(dāng)上面的代碼被編譯和執(zhí)行時(shí),它會(huì)產(chǎn)生下列結(jié)果: In Main: Creating the Child threadChild thread starts012In Main: Aborting the Child threadThread Abort ExceptionCouldn't catch the Thread Exception C# 多線(xiàn)程--愛(ài)整理一,、利用多線(xiàn)程提高程序性能本節(jié)導(dǎo)讀:隨著硬件和網(wǎng)絡(luò)的高速發(fā)展,為多線(xiàn)程(Multithreading)處理并行任務(wù),,提供了有利條件,。 其實(shí)我們每時(shí)每刻都在享受多線(xiàn)程帶來(lái)的便利,多核處理器多線(xiàn)程工作,、Windows操作系統(tǒng),、Web服務(wù)器都在使用多線(xiàn)程工作。 使用多線(xiàn)程直接提高了程序的執(zhí)行效率,,因此學(xué)習(xí)多線(xiàn)程對(duì)提高程序運(yùn)行能力非常必要,,本節(jié)主要介紹多線(xiàn)程原理及.NET中多線(xiàn)程在.NET面向?qū)ο蟪绦蛟O(shè)計(jì)中的應(yīng)用。 1. 關(guān)于多線(xiàn)程在介紹多線(xiàn)程之前,,先了解一下進(jìn)程,。 進(jìn)程:獨(dú)立運(yùn)行的程序稱(chēng)為進(jìn)程。(比如Windows系統(tǒng)后臺(tái)程序,,也可以稱(chēng)為后臺(tái)進(jìn)程) 線(xiàn)程:對(duì)于同一個(gè)程序分為多個(gè)執(zhí)行流,,稱(chēng)為線(xiàn)程。 多線(xiàn)程:使用多個(gè)線(xiàn)程進(jìn)行多任務(wù)處理,,稱(chēng)為多線(xiàn)程,。 并發(fā)是針對(duì)于單核處理器而言,但是目前市場(chǎng)上的CPU是多核的(一個(gè)芯片多個(gè)CPU核心),,多線(xiàn)程設(shè)計(jì)可以讓多個(gè)任務(wù)分發(fā)到多個(gè)CPU上并行執(zhí)行,,可以讓程序更快的執(zhí)行。但是并發(fā)通常是提高運(yùn)行在單核處理器上的程序的性能,。這么說(shuō)感覺(jué)有點(diǎn)違背直覺(jué),,因?yàn)樵趩魏颂幚砥魃线\(yùn)行并發(fā)程序開(kāi)銷(xiāo)要比順序執(zhí)行該程序的開(kāi)銷(xiāo)要大(從上到下順序執(zhí)行程序),因?yàn)椴l(fā)程序中增加了上下文切換的代價(jià)(一個(gè)線(xiàn)程切換到另外一個(gè)線(xiàn)程),,從表面上看如果順序執(zhí)行所有程序反而節(jié)省了上下文切換的代價(jià),。 讓這個(gè)問(wèn)題變得不同的是阻塞,程序中的某個(gè)任務(wù)因?yàn)樵摮绦蚩刂品秶獾囊恍l件(通常是I/O),整個(gè)程序就會(huì)停止下來(lái),,直到外部條件發(fā)生變化,。此時(shí)多線(xiàn)程的優(yōu)勢(shì)就會(huì)體現(xiàn)出來(lái)了,其他任務(wù)可以通過(guò)獲得CPU時(shí)間而繼續(xù)執(zhí)行,,而不會(huì)讓整個(gè)程序停下來(lái),。從性能的角度來(lái)看,如果沒(méi)有線(xiàn)程阻塞,,那么在單核處理器上使用并發(fā)那將毫無(wú)意義,。 2. 如何合理使用多線(xiàn)程?A.對(duì)于用戶(hù)等待程序處理時(shí),可以使用多線(xiàn)程處理耗時(shí)任務(wù),; B.對(duì)于一些不需要即時(shí)完成的任務(wù),,可以使用后臺(tái)任務(wù)線(xiàn)程處理; C.對(duì)于多并發(fā)任務(wù),,可以使用多線(xiàn)程同時(shí)處理,; 這一句的意思是讓并發(fā)線(xiàn)程變成并行線(xiàn)程嗎?即讓原本是單核處理多個(gè)線(xiàn)程,變成多核處理多個(gè)線(xiàn)程(一核分配一個(gè)線(xiàn)程) D.對(duì)于通訊類(lèi),比如對(duì)線(xiàn)程阻塞,,可以使用多線(xiàn)程,。 除過(guò)上面的幾個(gè)常用的情況,還有很多情況下可以使用多線(xiàn)程,。 3. 多線(xiàn)程的缺點(diǎn)線(xiàn)程自然也有缺點(diǎn),,以下列出了一些: A.如果有大量的線(xiàn)程,會(huì)影響性能,,因?yàn)椴僮飨到y(tǒng)需要在他們之間切換,; B.更多的線(xiàn)程需要更多的內(nèi)存空間; C.線(xiàn)程會(huì)給程序帶來(lái)更多的bug,,因此要小心使用,,比如:線(xiàn)程任務(wù)在執(zhí)行完成后,要及時(shí)釋放內(nèi)存,; D.線(xiàn)程的中止需要考慮其對(duì)程序運(yùn)行的影響,。 4. .NET中的兩種多線(xiàn)程.NET本身就是一個(gè)多線(xiàn)程的的環(huán)境。 在.NET中有兩種多線(xiàn)程的: 一種是使用Thread類(lèi)進(jìn)行線(xiàn)程的創(chuàng)建,、啟動(dòng),,終止等操作。 一種是使用ThreadPool類(lèi)用于管理線(xiàn)程池. 5 .NET中使用Thread進(jìn)行多線(xiàn)程處理線(xiàn)性池是一種多線(xiàn)程并發(fā)的處理形式,,它就是由一堆已創(chuàng)建好的線(xiàn)程組成,。有新任務(wù) -> 取出空閑線(xiàn)程處理任務(wù) -> 任務(wù)處理完成放入線(xiàn)程池等待。避免了處理短時(shí)間任務(wù)時(shí)大量的線(xiàn)程重復(fù)創(chuàng)建,、銷(xiāo)毀的代價(jià),,非常適用于連續(xù)產(chǎn)生大量并發(fā)任務(wù)的場(chǎng)合。 5.1 Thread類(lèi)常用方法.NET基礎(chǔ)類(lèi)庫(kù)的System.Threading命名空間提供了大量的類(lèi)和接口支持多線(xiàn)程,。System.Threading.Thread類(lèi)是創(chuàng)建并控制線(xiàn)程,,設(shè)置其優(yōu)先級(jí)并獲取其狀態(tài)最為常用的類(lèi)。 下面是該類(lèi)幾個(gè)至關(guān)重要的方法: Thread.Start():?jiǎn)?dòng)線(xiàn)程的執(zhí)行,; Thread.Suspend():掛起線(xiàn)程,,或者如果線(xiàn)程已掛起,則不起作用,; Thread.Resume():繼續(xù)已掛起的線(xiàn)程,; Thread.Interrupt():中止處于Wait或者Sleep或者Join線(xiàn)程狀態(tài)的線(xiàn)程,; Thread.Join():阻塞調(diào)用線(xiàn)程,直到某個(gè)線(xiàn)程終止時(shí)為止 Thread.Sleep():將當(dāng)前線(xiàn)程阻塞指定的毫秒數(shù),; Thread.Abort():終止此線(xiàn)程,。如果線(xiàn)程已經(jīng)在終止,則不能通過(guò)Thread.Start()來(lái)啟動(dòng)線(xiàn)程,。 suspend 掛起,、暫停 resume 繼續(xù)、重新開(kāi)始 interrupt 中斷,、打斷 5.2 Thread類(lèi)常用屬性Thread的屬性有很多,我們先看最常用的幾個(gè): CurrentThread :用于獲取當(dāng)前線(xiàn)程; ThreadState 當(dāng)前線(xiàn)程的狀態(tài)(5.4介紹),; Name:獲取或設(shè)置線(xiàn)程名稱(chēng),; Priority:獲取或設(shè)置線(xiàn)程的優(yōu)先級(jí)(5.5介紹) ManagedThreadId:獲取當(dāng)前線(xiàn)程的唯一標(biāo)識(shí) IsBackground:獲取或設(shè)置線(xiàn)程是前臺(tái)線(xiàn)程還是后臺(tái)線(xiàn)程(5.6介紹) IsThreadPoolThread:獲取當(dāng)前線(xiàn)程是否是托管線(xiàn)程池(后面章節(jié)會(huì)介紹) 下面創(chuàng)建一個(gè)線(xiàn)程示例,來(lái)說(shuō)明這幾個(gè)屬性: Thread myThreadTest = new Thread(() =>//這里的new Thread(ThreadStart start)里面ThreadStart是一個(gè)委托,而(()=>{代碼塊...})是lambda表達(dá)式,所以可以說(shuō)lambda表達(dá)式是基于委托的{ Thread.Sleep(1000); Thread t = Thread.CurrentThread; Console.WriteLine('Name: ' + t.Name); Console.WriteLine('ManagedThreadId: ' + t.ManagedThreadId); Console.WriteLine('State: ' + t.ThreadState); Console.WriteLine('Priority: ' + t.Priority); Console.WriteLine('IsBackground: ' + t.IsBackground); Console.WriteLine('IsThreadPoolThread: ' + t.IsThreadPoolThread);//process 進(jìn)程}){ Name = '線(xiàn)程測(cè)試', Priority = ThreadPriority.Highest};myThreadTest.Start();Console.WriteLine('關(guān)聯(lián)進(jìn)程的運(yùn)行的線(xiàn)程數(shù)量:'+System.Diagnostics.Process.GetCurrentProcess().Threads.Count); 運(yùn)行結(jié)果如下: 我的天竟然有6個(gè)線(xiàn)程,其他四個(gè)線(xiàn)程是? 下面的代碼是一個(gè)小插曲,有助于強(qiáng)化理解線(xiàn)程委托 using System;using System.Threading;namespace MultithreadingApplication{ delegate void P1(object n); delegate void P2(int n1, int n2); class ThreadCreationProgram { public static void MyThreadStart0() { Console.WriteLine('我的線(xiàn)程:0'); } public static void MyThreadStart11(object n) { for (int i = 0; i < (int)n; i++) { Console.WriteLine('我的線(xiàn)程:' + i); } } public static void MyThreadStart12(int n) { for (int i = 0; i < n; i++) { Console.WriteLine('我的線(xiàn)程:' + i); } } public static void MyThreadStart2(int n1, int n2) { for (int i = n1; i < n2; i++) { Console.WriteLine('我的線(xiàn)程:' + i); } } static void Main(string[] args) { //平常情況(自定義委托(參數(shù),返回值情況任意)) P1 p1 = new P1(MyThreadStart11); p1(5); P2 p2 = new P2(MyThreadStart2); p2(2, 5); //線(xiàn)程情況(系統(tǒng)線(xiàn)程定義委托(總共兩個(gè):1無(wú)參無(wú)返回值 2有一個(gè)參無(wú)返回值) ThreadStart ts = new ThreadStart(MyThreadStart0); ts(); ParameterizedThreadStart pts = new ParameterizedThreadStart(MyThreadStart11);//實(shí)例委托pts就相當(dāng)于一個(gè)方法指針,指向一個(gè)方法 pts(5); //我的天在線(xiàn)程中委托的參數(shù)居然是從start()里面?zhèn)鬟M(jìn)去的 new Thread(pts).Start(5); //注意下面兩個(gè)的比較 new Thread((n) => MyThreadStart11(n)).Start(5);//lambda表達(dá)式:(n) => MyThreadStart11(n)就像當(dāng)于委托public delegate void ParameterizedThreadStart(object obj)的一個(gè)實(shí)例,所以傳進(jìn)的參數(shù)n就是object類(lèi)型,下面的語(yǔ)句需要(int)n進(jìn)行強(qiáng)制轉(zhuǎn)換. new Thread((n) => MyThreadStart12((int)n)).Start(5);//委托的方法參數(shù)類(lèi)型與委托的參數(shù)數(shù)據(jù)類(lèi)型不一致,此處卻可以這樣搞,就當(dāng)是封裝的原因吧 new Thread(MyThreadStart0);//甚至可以直接跟個(gè)方法名里面的括號(hào)都省略了 Console.ReadKey(); } }} 5.3 帶參數(shù)的線(xiàn)程方法首先我們寫(xiě)“簡(jiǎn)單線(xiàn)程”中無(wú)參數(shù)的方法,,如下: 注意看注釋 namespace MultithreadingApplication{ class ThreadCreationProgram { static void MyThreadStart() { Console.WriteLine('我是一個(gè)簡(jiǎn)單線(xiàn)程'); } static void Main(string[] args) { //簡(jiǎn)單的線(xiàn)程 Thread myThread = new Thread(MyThreadStart);//此時(shí)的Thread(MyThreadStart)等于Thread(() =>MyThreadStart()),可能是lambda express的簡(jiǎn)寫(xiě)形式 //那么也就是說(shuō)在無(wú)參方法調(diào)用委托時(shí)可以不用先實(shí)例化一個(gè)委托ThreadStart ts = new ThreadStart(MyThreadStart)然后再將ts傳進(jìn)線(xiàn)程Thread myThread = new Thread(ts),而可以直接在線(xiàn)程里傳此方法Thread myThread = new Thread(MyThreadStart),這樣的話(huà)就簡(jiǎn)單一些了 myThread.Start(); } }} 我們使用Lambda表達(dá)式來(lái)改寫(xiě)前面“簡(jiǎn)單線(xiàn)程”中無(wú)參數(shù)的方法,,如下: namespace MultithreadingApplication{ class ThreadCreationProgram { static void Main(string[] args) { new Thread(() => { //此處可以說(shuō)用無(wú)參的方法調(diào)用委托 for (int i = 0; i < 5; i++) Console.WriteLine('我的線(xiàn)程一-[{0}]', i); }).Start(); Console.ReadKey(); } }} 上面示例創(chuàng)建的線(xiàn)程并沒(méi)有帶參數(shù),如果是一個(gè)有參數(shù)的方法,,線(xiàn)程該如何創(chuàng)建,? 別擔(dān)心,.NET為我們提供了一個(gè)ParameterizedThreadStart 委托 來(lái)解決帶一個(gè)參數(shù)的問(wèn)題,,如下: new Thread((num) =>{ for (int i = 0; i < (int)num; i++) Console.WriteLine('我的線(xiàn)程二--[{0}]', i);}).Start(5);/*由于ParameterizedThreadStart 委托傳的值是object類(lèi)型的,所以要強(qiáng)制轉(zhuǎn)化一下*/ 運(yùn)行結(jié)果如下: 那么問(wèn)題來(lái)了,,ParameterizedThreadStart委托只有一個(gè)包含數(shù)據(jù)的參數(shù), 對(duì)于多個(gè)參數(shù)呢,?我們可以使用一個(gè)無(wú)參數(shù)的方法來(lái)包裝它,,如下: 先創(chuàng)建一個(gè)帶參數(shù)的方法: static void myThreadStart(int numA, int numB){ for (int i = (int)numA; i < (int)numB; i++) Console.WriteLine('我的線(xiàn)程三---[{0}]', i);} 然后通過(guò)無(wú)參數(shù)的委托來(lái)包裝它,如下 : //這里默認(rèn)匹配是public delegate void ThreadStart();的一個(gè)實(shí)例new Thread(() => myThreadStart(0, 5)).Start();//事到如今我感覺(jué)可以肯定的說(shuō)lambda表達(dá)式實(shí)質(zhì)上就是一個(gè)委托 運(yùn)行結(jié)果如下: 5.4 Thread狀態(tài)我們對(duì)于線(xiàn)程啟動(dòng)以后,,如何進(jìn)行掛起和終止,、重新啟用,首先線(xiàn)程在運(yùn)行后有一個(gè)狀態(tài),。 System.Threading.Thread.ThreadState屬性定義了執(zhí)行時(shí)線(xiàn)程的狀態(tài),。線(xiàn)程從創(chuàng)建到線(xiàn)程終止,它一定處于其中某一個(gè)狀態(tài),。 A.Unstarted:當(dāng)線(xiàn)程被創(chuàng)建時(shí),,它處在Unstarted狀態(tài)。 B.Running:Thread類(lèi)的Start() 方法將使線(xiàn)程狀態(tài)變?yōu)镽unning狀態(tài),,線(xiàn)程將一直處于這樣的狀態(tài),,除非我們調(diào)用了相應(yīng)的方法使其掛起、阻塞,、銷(xiāo)毀或者自然終止,。 C.Suspended:如果線(xiàn)程被掛起,,它將處于Suspended狀態(tài)。 D.Running:我們調(diào)用Resume()方法使其重新執(zhí)行,,這時(shí)候線(xiàn)程將重新變?yōu)镽unning狀態(tài),。 E.Stopped:一旦線(xiàn)程被銷(xiāo)毀或者終止,線(xiàn)程處于Stopped狀態(tài),。處于這個(gè)狀態(tài)的線(xiàn)程將不復(fù)存在,,正如線(xiàn)程開(kāi)始啟動(dòng),線(xiàn)程將不可能回到Unstarted狀態(tài),。 F.Background:線(xiàn)程還有一個(gè)Background狀態(tài),,它表明線(xiàn)程運(yùn)行在前臺(tái)還是后臺(tái)。在一個(gè)確定的時(shí)間,,線(xiàn)程可能處于多個(gè)狀態(tài),。 G.WaitSleepJoin、AbortRequested:舉例子來(lái)說(shuō),,一個(gè)線(xiàn)程被調(diào)用了Sleep而處于阻塞,,而接著另外一個(gè)線(xiàn)程調(diào)用Abort方法于這個(gè)阻塞的線(xiàn)程,這時(shí)候線(xiàn)程將同時(shí)處于WaitSleepJoin和AbortRequested狀態(tài),。 H.一旦線(xiàn)程響應(yīng)轉(zhuǎn)為Sleep阻塞或者中止,,當(dāng)銷(xiāo)毀時(shí)會(huì)拋出ThreadAbortException異常。 ThreadState枚舉的10種執(zhí)行狀態(tài)如下: 上圖了解一個(gè)WaitSleepJoin就可以了 monitor 監(jiān)視器,監(jiān)聽(tīng)器,監(jiān)控器 對(duì)于線(xiàn)程阻塞和同步問(wèn)題,,將在下一節(jié)繼續(xù)介紹,。 5.5. 線(xiàn)程優(yōu)先級(jí)對(duì)于多線(xiàn)程任務(wù),我們可以根據(jù)其重要性和運(yùn)行所需要的資源情況,,設(shè)置他的優(yōu)先級(jí) System.Threading.ThreadPriority枚舉了線(xiàn)程的優(yōu)先級(jí)別,,從而決定了線(xiàn)程能夠得到多少CPU時(shí)間。 高優(yōu)先級(jí)的線(xiàn)程通常會(huì)比一般優(yōu)先級(jí)的線(xiàn)程得到更多的CPU時(shí)間 主線(xiàn)程與各種線(xiàn)程(高優(yōu)先級(jí),低優(yōu)先級(jí)線(xiàn)程)搶奪cup資源,一般優(yōu)先級(jí)越高搶到cup資源的概率越高,從而cpu占用的時(shí)間越多. 而不是每個(gè)線(xiàn)程一個(gè)一個(gè)排序來(lái),排到高優(yōu)先級(jí)線(xiàn)程時(shí)cup分配的時(shí)間多一些,排到低優(yōu)先級(jí)線(xiàn)程時(shí)cup分配的時(shí)間少一些, 新創(chuàng)建的線(xiàn)程優(yōu)先級(jí)為一般優(yōu)先級(jí),,我們可以設(shè)置線(xiàn)程的優(yōu)先級(jí)別的值,,如下面所示: 線(xiàn)程搶占cpu資源可以用如下代碼測(cè)試: static void Main(string[] args){ int numberH1 = 0,numberH2=0, numberL1 = 0, numberL2=0; bool state = true; new Thread(() => { while (state) { numberH1++; Console.WriteLine('H1'); }; }) { Priority = ThreadPriority.Highest, Name = '線(xiàn)程A' }.Start(); new Thread(() => { while (state) { numberH2++; Console.WriteLine('H2'); }; }) { Priority = ThreadPriority.Highest, Name = '線(xiàn)程A' }.Start(); new Thread(() => { while (state) { numberL1++; Console.WriteLine('L1'); }; }) { Priority = ThreadPriority.Lowest, Name = '線(xiàn)程B' }.Start(); //讓主線(xiàn)程掛件1秒 Thread.Sleep(1000); state = false; Console.WriteLine('線(xiàn)程H1: {0}, 線(xiàn)程H2: {1}, 線(xiàn)程L1: {2}', numberH1,numberH2,numberL1); Console.ReadKey();} 5.6 前臺(tái)線(xiàn)程和后臺(tái)線(xiàn)程線(xiàn)程有兩種,默認(rèn)情況下為前臺(tái)線(xiàn)程,,要想設(shè)置為后臺(tái)線(xiàn)程也非常容易,,只需要加一個(gè)屬性:thread.IsBackground = true;就可以變?yōu)橐粋€(gè)后臺(tái)線(xiàn)程了。 重點(diǎn)來(lái)了,,前后臺(tái)線(xiàn)程的區(qū)別: A.前臺(tái)線(xiàn)程:應(yīng)用程序必須執(zhí)行完所有的前臺(tái)線(xiàn)程才能退出,; B.后臺(tái)線(xiàn)程:應(yīng)用程序不必考慮其是否全部完成,可以直接退出,。應(yīng)用程序退出時(shí),,自動(dòng)終止后臺(tái)線(xiàn)程。 下面我們使用一個(gè)輸出從0到1000的數(shù)字,,來(lái)實(shí)驗(yàn)一下前臺(tái)線(xiàn)程和后臺(tái)線(xiàn)程的區(qū)別: static void Main(string[] args){ Thread myThread = new Thread(() => { for (int i = 0; i < 1000; i++) Console.WriteLine(i); }); var key = Console.ReadLine(); if (key == '1') { myThread.IsBackground = true; myThread.Start(); } else { myThread.IsBackground = false; myThread.Start(); }} 如果輸入1(后臺(tái)線(xiàn)程),,線(xiàn)程會(huì)很快關(guān)閉,,并不會(huì)等輸出完1000個(gè)數(shù)字再關(guān)閉; 如果輸入其它(前臺(tái)線(xiàn)程),,回車(chē)后,,則線(xiàn)程會(huì)等1000個(gè)數(shù)字輸出完后,窗口關(guān)閉,; 6. 本節(jié)要點(diǎn):A.本節(jié)主要介紹了線(xiàn)程的基本知識(shí),; B.Thread常用的屬性、方法; C.Thread委托的方法有多個(gè)參數(shù)的用法,; D.Thread的優(yōu)先級(jí),; E.Thread的執(zhí)行狀態(tài); F.前臺(tái)線(xiàn)程和后臺(tái)線(xiàn)程,; 后面會(huì)繼續(xù)深入介紹利用線(xiàn)程提高程序性能,。 二、多線(xiàn)程高級(jí)應(yīng)用本節(jié)要點(diǎn):上節(jié)介紹了多線(xiàn)程的基本使用方法和基本應(yīng)用示例,,本節(jié)深入介紹.NET多線(xiàn)程中的高級(jí)應(yīng)用。 主要有在線(xiàn)程資源共享中的線(xiàn)程安全和線(xiàn)程沖突的解決方案,;多線(xiàn)程同步,,使用線(xiàn)程鎖和線(xiàn)程通知實(shí)現(xiàn)線(xiàn)程同步。 1,、 ThreadStatic特性特性:[ThreadStatic] 功能:指定靜態(tài)字段在不同線(xiàn)程中擁有不同的值 在此之前,,我們先看一個(gè)多線(xiàn)程的示例: 我們定義一個(gè)靜態(tài)字段: static int num = 0;new Thread(() =>{ for (int i = 0; i < 1000000; i++) ++num; Console.WriteLine('來(lái)自{0}:{1}', Thread.CurrentThread.Name, num);}){ Name = '線(xiàn)程一' }.Start();隱藏代碼new Thread(() =>{ for (int i = 0; i < 2000000; i++) ++num; Console.WriteLine('來(lái)自{0}:{1}', Thread.CurrentThread.Name, num);}){ Name = '線(xiàn)程二' }.Start(); 運(yùn)行多次結(jié)果如下:
可以看到,三次的運(yùn)行結(jié)果均不相同,產(chǎn)生這種問(wèn)題的原因是多線(xiàn)程中同步共享問(wèn)題導(dǎo)致的,,即是多個(gè)線(xiàn)程同時(shí)共享了一個(gè)資源,。 此處代碼與上下文無(wú)關(guān),知識(shí)兩個(gè)疑惑,注意看注釋 using System;using System.Threading;namespace MultithreadingApplication{ class ThreadCreationProgram { static int num = 0; static void Main(string[] args) { new Thread(() => { int k=0; for (k = 0; k < 100000; k++) ++num; Console.WriteLine('來(lái)自{0}:{1} 此時(shí)k的值為{2}', Thread.CurrentThread.Name, num,k); }) { Name = '線(xiàn)程一' }.Start(); new Thread(() => { int j = 0; for (; j < 200000; j++) ++num; Console.WriteLine('來(lái)自{0}:{1} 此時(shí)j的值為{2}', Thread.CurrentThread.Name, num,j); }) { Name = '線(xiàn)程二' }.Start(); Thread.Sleep(5*1000); Console.WriteLine('主線(xiàn)程又開(kāi)始'); Console.ReadKey(); } } //疑惑 1 兩個(gè)線(xiàn)程執(zhí)行次數(shù)竟然大于3000000,也竟然有小于3000000的 //2 竟然會(huì)輸出:來(lái)自線(xiàn)程一:56265 答;這是因?yàn)椴粌H只有兩個(gè)線(xiàn)程在執(zhí)行,還有個(gè)主線(xiàn)程在執(zhí)行,不要忽略了. //因?yàn)橹骶€(xiàn)程走到了Console.ReadKey(),所以會(huì)在控制臺(tái)輸出線(xiàn)程一還未走完的num值,此時(shí)num值也就小于1000000了 //那么為了避免主線(xiàn)程對(duì)子線(xiàn)程的影響可以阻塞主線(xiàn)程一段時(shí)間知道子線(xiàn)程完成(用sleep方法)--我的天吶我發(fā)現(xiàn)排除了主線(xiàn)程readkey的干擾后 //仍然會(huì)輸出:來(lái)自線(xiàn)程一:989265的情況者,這發(fā)生了什么?} 如何解決上述問(wèn)題,最簡(jiǎn)單的方法就是使用靜態(tài)字段的ThreadStatic特性,。 在定義靜態(tài)字段時(shí),,加上[ThreadStatic]特性,如下: [ThreadStatic]static int num = 0; 兩個(gè)線(xiàn)程不變的情況下,,再次運(yùn)行,,結(jié)果如下: 不論運(yùn)行多少次,結(jié)果都是一樣的,,當(dāng)字段被ThreadStatic特性修飾后,,它的值在每個(gè)線(xiàn)程中都是不同的,即每個(gè)線(xiàn)程對(duì)static字段都會(huì)重新分配內(nèi)存空間,,就當(dāng)然于一次new操作,,這樣一來(lái),由于static字段所產(chǎn)生的問(wèn)題也就沒(méi)有了,。 2. 資源共享多線(xiàn)程的資源共享,,也就是多線(xiàn)程同步(即資源同步),,需要注意的是線(xiàn)程同步指的是線(xiàn)程所訪(fǎng)問(wèn)的資源同步,并非是線(xiàn)程本身的同步,。 在實(shí)際使用多線(xiàn)程的過(guò)程中,,并非都是各個(gè)線(xiàn)程訪(fǎng)問(wèn)不同的資源。 下面看一個(gè)線(xiàn)程示例,,假如我們并不知道線(xiàn)程要多久完成,,我們等待一個(gè)固定的時(shí)間(假如是500毫秒): 先定義一個(gè)靜態(tài)字段: static int result;Thread myThread = new Thread(() =>{ Thread.Sleep(1000); result = 100;});myThread.Start();Thread.Sleep(500); Console.WriteLine(result); 運(yùn)行結(jié)果如下: 可以看到結(jié)果是0,顯然不是我們想要的,,但往往在線(xiàn)程執(zhí)行過(guò)程中,,我們并不知道它要多久完成,能不能在線(xiàn)程完成后有一個(gè)通知,? 下面的代碼與上下文無(wú)關(guān),只是一個(gè)小注意點(diǎn) using System;using System.Threading;namespace MultithreadingApplication{ class ThreadCreationProgram { static int result; static void Main(string[] args) { Thread myThread = new Thread(() => { Thread.Sleep(10000); result = 100; Console.WriteLine(result); Console.ReadKey(); }); myThread.Start();//這一步再往下走兩個(gè)線(xiàn)程就開(kāi)始搶奪cup資源了 Thread.Sleep(1000); Console.WriteLine(result); Console.ReadKey();//執(zhí)行完這一步,并不會(huì)就一直停在這里,當(dāng)myThread線(xiàn)程睡眠時(shí)間到了,會(huì)自動(dòng)執(zhí)行myThread線(xiàn)程 //然后停在myThread的Readkey處,在控制臺(tái)輸入任意值,走到24,再輸入任意值,走到18. } }} .NET為我們提供了一個(gè)Join方法,,就是線(xiàn)程阻塞,可以解決上述問(wèn)題,,我們使用Stopwatch來(lái)記時(shí) 改進(jìn)線(xiàn)程代碼如下: using System;using System.Threading;namespace MultithreadingApplication{ class ThreadCreationProgram { static int result; static void Main(string[] args) { //Diagnostic 診斷. Stopwatch 跑表 //StartNew()初始化新的 System.Diagnostics.Stopwatch 實(shí)例,,將運(yùn)行時(shí)間屬性設(shè)置為零,然后開(kāi)始測(cè)量運(yùn)行時(shí)間,。 System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew(); Thread myThread = new Thread(() => { Thread.Sleep(1000); result = 100; }); myThread.Start(); Thread.Sleep(500);//走到這一步主線(xiàn)程睡眠,進(jìn)入子線(xiàn)程myThread myThread.Join();//0.5秒鐘后回到主線(xiàn)程這一步,走到這一步時(shí)會(huì)停下來(lái)直到子線(xiàn)程myThread執(zhí)行完畢. Console.WriteLine(watch.ElapsedMilliseconds);//Elapsed 消逝,、過(guò)去 Millisecond 毫秒 Console.WriteLine(result); Console.ReadKey(); } }}//Join()和sleep()都是線(xiàn)程阻塞 運(yùn)行結(jié)果如下: 結(jié)果和我們想要的是一致的。 3. 線(xiàn)程鎖除了上面示例的方法,,對(duì)于線(xiàn)程同步,,.NET還為我們提供了一個(gè)鎖機(jī)制來(lái)解決同步,再次改進(jìn)上面示例如下: using System;using System.Threading;namespace MultithreadingApplication{ class ThreadCreationProgram { //先定義一個(gè)靜態(tài)字段來(lái)存儲(chǔ)鎖 static object locker = new object(); static int result; static void Main(string[] args) { System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew(); Thread t1 = new Thread(() => { lock (locker)//lock (x)里面的x是引用類(lèi)型 { Thread.Sleep(10000); result = 100; } }); t1.Start(); Thread.Sleep(5000); lock (locker)//lock(x)中的x是同一個(gè)引用類(lèi)型的變量時(shí),這些鎖之間是互斥的,只有最先執(zhí)行的鎖執(zhí)行完,才會(huì)執(zhí)行下一個(gè)鎖 { Console.WriteLine('線(xiàn)程耗時(shí):' + watch.ElapsedMilliseconds); Console.WriteLine('線(xiàn)程輸出:' + result); } Console.ReadKey(); } }} 運(yùn)行結(jié)果如下: 運(yùn)行結(jié)果和上面示例一樣,,如果線(xiàn)程處理過(guò)程較復(fù)雜,,可以看到耗時(shí)明顯減少,這是一種用比阻塞更效率的方式完成線(xiàn)程同步,。 4. 線(xiàn)程通知前面說(shuō)到了能否在一個(gè)線(xiàn)程完成后,,通知等待的線(xiàn)程呢,這里.NET為我們提供了一個(gè)事件通知的方法來(lái)解決這個(gè)問(wèn)題,。 4.1 AutoResetEvent改進(jìn)上面的線(xiàn)程如下: using System;using System.Threading;//一個(gè)線(xiàn)程完成后通知另外一個(gè)線(xiàn)程(是一個(gè)!與下面的幾個(gè)不同)namespace MultithreadingApplication{ class ThreadCreationProgram { //先定義一個(gè)通知對(duì)象 //EventWaitHandle 表示一個(gè)線(xiàn)程同步事件,。 static EventWaitHandle tellMe = new AutoResetEvent(false);//里面的boolean該值指示是否將初始狀態(tài)設(shè)置為終止?fàn)顟B(tài)的類(lèi)。 static int result = 0; static void Main(string[] args) { System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew(); Thread myThread = new Thread(() => { Thread.Sleep(5000); result = 100; tellMe.Set();//將事件狀態(tài)設(shè)置為有信號(hào),,從而允許一個(gè)或多個(gè)等待線(xiàn)程繼續(xù)執(zhí)行,。 }); myThread.Start(); tellMe.WaitOne();//阻止當(dāng)前線(xiàn)程,直到當(dāng)前 System.Threading.WaitHandle 收到信號(hào),。 Console.WriteLine('線(xiàn)程耗時(shí):' + watch.ElapsedMilliseconds); Console.WriteLine('線(xiàn)程輸出:' + result); } }//待在同一個(gè)代碼塊的兩個(gè)線(xiàn)程是資源共享的,即兩個(gè)線(xiàn)程是同步的} 運(yùn)行結(jié)果如下: 4.2 ManualResetEvent和AutoResetEvent 相對(duì)的還有一個(gè) ManualResetEvent 手動(dòng)模式,,他們的區(qū)別在于,在線(xiàn)程結(jié)束后ManualResetEvent 還是可以通行的,,除非手動(dòng)Reset關(guān)閉,。下面看一個(gè)示例: 這句話(huà)的意思是在mre.Set()和mre.WaitOne()執(zhí)行完之后,如果有另一個(gè)mre.WaitOne(),此時(shí)仍可以通過(guò).如果是AutoResetEvent的話(huà)就不可以了,可以將下面的代碼ManualResetEvent改成AutoResetEvent試一下 using System;using System.Threading;namespace MultithreadingApplication{ //一個(gè)線(xiàn)程完成後通知其他個(gè)線(xiàn)程.(其他的意思是多于一個(gè)) class ThreadCreationProgram { //EventWaitHandle 表示一個(gè)線(xiàn)程同步事件,。 static EventWaitHandle mre = new ManualResetEvent(false);//布爾值指示是否將初始狀態(tài)設(shè)置為終止?fàn)顟B(tài)的類(lèi)。 static int result = 0; static void Main(string[] args) { System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew(); Thread myThreadFirst = new Thread(() => { Thread.Sleep(10000); result = 100; mre.Set();//將事件狀態(tài)設(shè)置為有信號(hào),,從而允許一個(gè)或多個(gè)等待線(xiàn)程繼續(xù)執(zhí)行,。 }) { Name = '線(xiàn)程一' }; Thread myThreadSecond = new Thread(() => { 兩個(gè)WaitOne()執(zhí)行后進(jìn)入線(xiàn)程一執(zhí)行Set(),Set執(zhí)行后代表兩個(gè)WaitOne都已經(jīng)通過(guò) mre.WaitOne(); Console.WriteLine(Thread.CurrentThread.Name + '獲取結(jié)果:' + result + '(' + System.DateTime.Now.ToString() + ')'); }) { Name = '線(xiàn)程二' }; myThreadFirst.Start(); myThreadSecond.Start(); mre.WaitOne();//阻止當(dāng)前線(xiàn)程,直到當(dāng)前 System.Threading.WaitHandle 收到信號(hào),。 Console.WriteLine('線(xiàn)程耗時(shí):' + watch.ElapsedMilliseconds + '(' + System.DateTime.Now.ToString() + ')'); Console.WriteLine('線(xiàn)程輸出:' + result + '(' + System.DateTime.Now.ToString() + ')'); Console.ReadKey(); } }//手動(dòng)Reset關(guān)閉,mre.Reset();} 運(yùn)行結(jié)果如下: 線(xiàn)程二獲取結(jié)果:100可能先輸出,或者在中間輸出,也可能最后輸出,這取決于主線(xiàn)程與線(xiàn)程二對(duì)cpu資源的搶奪 下面代碼是手動(dòng) Reset()關(guān)閉展示 using System;using System.Threading;namespace MultithreadingApplication{ //一個(gè)線(xiàn)程完成後通知其他個(gè)線(xiàn)程.(其他的意思是多于一個(gè)) class ThreadCreationProgram { //EventWaitHandle 表示一個(gè)線(xiàn)程同步事件,。 static EventWaitHandle mre = new ManualResetEvent(false);//布爾值指示是否將初始狀態(tài)設(shè)置為終止?fàn)顟B(tài)的類(lèi)。 static int result = 0; static void Main(string[] args) { System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew(); Thread myThreadFirst = new Thread(() => { Thread.Sleep(1000); result = 100; mre.Set();//將事件狀態(tài)設(shè)置為有信號(hào),,從而允許一個(gè)或多個(gè)等待線(xiàn)程繼續(xù)執(zhí)行,。 }) { Name = '線(xiàn)程一' }; Thread myThreadSecond = new Thread(() => { mre.WaitOne();//兩個(gè)WaitOne()同時(shí)執(zhí)行后進(jìn)入線(xiàn)程一執(zhí)行Set(),Set執(zhí)行后代表兩個(gè)WaitOne都已經(jīng)通過(guò) Console.WriteLine(Thread.CurrentThread.Name + '獲取結(jié)果:' + result + '(' + System.DateTime.Now.ToString() + ')'); }) { Name = '線(xiàn)程二' }; Thread myThreadThird = new Thread(() => { mre.Reset(); mre.WaitOne(); Console.WriteLine(Thread.CurrentThread.Name + '獲取結(jié)果:' + result + '(' + System.DateTime.Now.ToString() + ')'); }) { Name = '線(xiàn)程三' }; myThreadFirst.Start(); myThreadSecond.Start(); mre.WaitOne();//阻止當(dāng)前線(xiàn)程,直到當(dāng)前 System.Threading.WaitHandle 收到信號(hào),。 Console.WriteLine('線(xiàn)程耗時(shí):' + watch.ElapsedMilliseconds + '(' + System.DateTime.Now.ToString() + ')'); Console.WriteLine('線(xiàn)程輸出:' + result + '(' + System.DateTime.Now.ToString() + ')'); myThreadThird.Start(); Thread.Sleep(1000); mre.Set();//將這一句注釋掉線(xiàn)程三WaitOne()就等不到信號(hào),從而會(huì)被一直阻塞. Console.ReadKey(); } }} 4.3. SemaphoreSemaphore也是線(xiàn)程通知的一種,,上面的通知模式,在線(xiàn)程開(kāi)啟的數(shù)量很多的情況下,,使用Reset()關(guān)閉時(shí),,如果不使用Sleep休眠一下,很有可能導(dǎo)致某些線(xiàn)程沒(méi)有恢復(fù)的情況下,,某一線(xiàn)程提前關(guān)閉,,對(duì)于這種很難預(yù)測(cè)的情況,.NET提供了更高級(jí)的通知方式Semaphore,可以保證在超多線(xiàn)程時(shí)不會(huì)出現(xiàn)上述問(wèn)題,。 using System;using System.Threading;//semaphor 發(fā)信號(hào),打旗語(yǔ)namespace MultithreadingApplication{ class Program { //先定義一個(gè)通知對(duì)象的靜態(tài)字段 //Semaphore(初始授予1個(gè)請(qǐng)求數(shù),設(shè)置最大可授予5個(gè)請(qǐng)求數(shù)) static Semaphore semaphore = new Semaphore(1, 5);//初始授予1個(gè)請(qǐng)求數(shù),如果沒(méi)有semaphore.Release()語(yǔ)句,則只會(huì)執(zhí)行一個(gè)子線(xiàn)程,執(zhí)行完之后請(qǐng)求數(shù)又會(huì)變成0 static void Main(string[] args) { for (int i = 1; i <= 5; i++) { Thread thread = new Thread(Work); thread.Start(i); } Thread.Sleep(2000); //授予3個(gè)請(qǐng)求 semaphore.Release(3); Console.ReadLine(); } static void Work(object obj) { semaphore.WaitOne(); Console.WriteLine('print: {0}', obj); } }//程序執(zhí)行完畢會(huì)輸出四個(gè)記錄} 5. 本節(jié)要點(diǎn):A.線(xiàn)程中靜態(tài)字段的ThreadStatic特性,,使用該字段在不同線(xiàn)程中擁有不同的值 B.線(xiàn)程同步的幾種方式,,線(xiàn)程鎖和線(xiàn)程通知 C.線(xiàn)程通知的兩種方式:AutoResetEvent /ManualResetEvent 和 Semaphore 多線(xiàn)程的更多特性,下一節(jié)繼續(xù)深入介紹,。 三,、利用多線(xiàn)程提高程序性能(下)本節(jié)導(dǎo)讀:上節(jié)說(shuō)了線(xiàn)程同步中使用線(xiàn)程鎖和線(xiàn)程通知的方式來(lái)處理資源共享問(wèn)題,這些是多線(xiàn)程的基本原理,。 .NET 4.0 以后對(duì)多線(xiàn)程的實(shí)現(xiàn)變得更簡(jiǎn)單了,。 本節(jié)主要討論 .NET4.0 多線(xiàn)程的新特性——使用 Task類(lèi)創(chuàng)建多線(xiàn)程。 讀前必備:A. LINQ使用 [.net 面向?qū)ο缶幊袒A(chǔ)] (20) LINQ使用 B. 泛型 [.net 面向?qū)ο缶幊袒A(chǔ)] (18) 泛型 1.線(xiàn)程池ThreadPool在介紹4.0以后的多線(xiàn)程新特征之前,,先簡(jiǎn)單說(shuō)一下線(xiàn)程池,。 通過(guò)前面對(duì)多線(xiàn)程的學(xué)習(xí),我們發(fā)現(xiàn)多線(xiàn)程的創(chuàng)建和使用并不難,,難的在于多線(xiàn)程的管理,,特別是線(xiàn)程數(shù)量級(jí)很多的情況下,如何進(jìn)行管理和資源釋放,。需要使用線(xiàn)程池來(lái)解決,。 簡(jiǎn)單來(lái)說(shuō)線(xiàn)程池就是.NET提供的存放線(xiàn)程的一個(gè)對(duì)象容器,。 為什么要使用線(xiàn)性池 微軟官網(wǎng)說(shuō)法如下:許多應(yīng)用程序創(chuàng)建大量處于睡眠狀態(tài),等待事件發(fā)生的線(xiàn)程,。還有許多線(xiàn)程可能會(huì)進(jìn)入休眠狀態(tài),,這些線(xiàn)程只是為了定期喚醒以輪詢(xún)更改或更新的狀態(tài)信息。 線(xiàn)程池,,使您可以通過(guò)由系統(tǒng)管理的工作線(xiàn)程池來(lái)更有效地使用線(xiàn)程,。 說(shuō)得簡(jiǎn)單一點(diǎn),每新建一個(gè)線(xiàn)程都需要占用內(nèi)存空間和其他資源,,而新建了那么多線(xiàn)程,,有很多在休眠,或者在等待資源釋放,;又有許多線(xiàn)程只是周期性的做一些小工作,,如刷新數(shù)據(jù)等等,太浪費(fèi)了,,劃不來(lái),,實(shí)際編程中大量線(xiàn)程突發(fā),然后在短時(shí)間內(nèi)結(jié)束的情況很少見(jiàn),。于是,,就提出了線(xiàn)程池的概念。線(xiàn)程池中的線(xiàn)程執(zhí)行完指定的方法后并不會(huì)自動(dòng)消除,,而是以?huà)炱馉顟B(tài)返回線(xiàn)程池,,如果應(yīng)用程序再次向線(xiàn)程池發(fā)出請(qǐng)求,那么處以?huà)炱馉顟B(tài)的線(xiàn)程就會(huì)被激活并執(zhí)行任務(wù),,而不會(huì)創(chuàng)建新線(xiàn)程,,這就節(jié)約了很多開(kāi)銷(xiāo)。只有當(dāng)線(xiàn)程數(shù)達(dá)到最大線(xiàn)程數(shù)量,,系統(tǒng)才會(huì)自動(dòng)銷(xiāo)毀線(xiàn)程,。因此,使用線(xiàn)程池可以避免大量的創(chuàng)建和銷(xiāo)毀的開(kāi)支,,具有更好的性能和穩(wěn)定性,,其次,開(kāi)發(fā)人員把線(xiàn)程交給系統(tǒng)管理,,可以集中精力處理其他任務(wù),。 線(xiàn)程池線(xiàn)程分為兩類(lèi):工作線(xiàn)程和 IO 線(xiàn)程 . 線(xiàn)程池是一種多線(xiàn)程處理形式,處理過(guò)程中將任務(wù)添加到隊(duì)列,,然后在創(chuàng)建線(xiàn)程后自動(dòng)啟動(dòng)這些任務(wù),。 下面是一個(gè)線(xiàn)程池的示例: using System;using System.Threading;namespace MultithreadingApplication{ class Program { //先設(shè)置一個(gè)創(chuàng)建線(xiàn)程總數(shù)靜態(tài)字段: static readonly int totalThreads = 20; static void Main(string[] args) { //線(xiàn)性池是靜態(tài)類(lèi)可以直接使用 //參數(shù)1:要由線(xiàn)程池根據(jù)需要?jiǎng)?chuàng)建的新的最小工作程序線(xiàn)程數(shù)。 //參數(shù)2:要由線(xiàn)程池根據(jù)需要?jiǎng)?chuàng)建的新的最小空閑異步 I/O 線(xiàn)程數(shù)。 ThreadPool.SetMinThreads(2, 2); //參數(shù)1:線(xiàn)程池中輔助線(xiàn)程的最大數(shù)目,。 //參數(shù)2:線(xiàn)程池中異步 I/O 線(xiàn)程的最大數(shù)目,。 ThreadPool.SetMaxThreads(20, 20); for (int i = 0; i < totalThreads; i++) { ThreadPool.QueueUserWorkItem(o => { Thread.Sleep(1000); int a, b; //參數(shù)1:可用輔助線(xiàn)程的數(shù)目。 //參數(shù)2:可用異步 I/O 線(xiàn)程的數(shù)目,。 ThreadPool.GetAvailableThreads(out a, out b); Console.WriteLine(string.Format('({0}/{1}) #{2} : {3}', a, b, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString())); }); } Console.WriteLine('主線(xiàn)程完成'); Console.ReadKey(); } }} 2. Task類(lèi)用 ThreadPool 的 QueueUserWorkItem() 方法發(fā)起一次異步的線(xiàn)程執(zhí)行很簡(jiǎn)單,,但是該方法最大的問(wèn)題是沒(méi)有一個(gè)內(nèi)建的機(jī)制讓你知道操作什么時(shí)候完成,有沒(méi)有一個(gè)內(nèi)建的機(jī)制在操作完成后獲得一個(gè)返回值,。為此,, 在.NET 4.0 以后,我們 可以使用 System.Threading.Tasks 中的 Task 類(lèi),。 這也是.NET 4.0 以后多線(xiàn)程的推薦做法,。 構(gòu)造一個(gè) Task<T> 對(duì)象,并為泛型 T 參數(shù)傳遞一個(gè)操作的返回類(lèi)型,。 Task類(lèi)可以使用多種方法創(chuàng)建多線(xiàn)程,,下面詳細(xì)介紹。 2.1 使用Factory屬性Task 實(shí)例可以用各種不同的方式創(chuàng)建,。 最常見(jiàn)的方法是使用任務(wù)的 Factory 屬性檢索可用來(lái)創(chuàng)建用于多個(gè)用途的 TaskFactory 實(shí)例,。 例如,要?jiǎng)?chuàng)建運(yùn)行操作的 Task ,,可以使用工廠(chǎng)的 StartNew 方法: //最簡(jiǎn)單的線(xiàn)程示例Task.Factory.StartNew(() =>{ Console.WriteLine('我是使用Factory屬性創(chuàng)建的線(xiàn)程');}); 如果想簡(jiǎn)單的創(chuàng)建一個(gè)Task,,那么使用Factory.StartNew()來(lái)創(chuàng)建,很簡(jiǎn)便,。 如果像對(duì)所創(chuàng)建的Task附加更多的定制和設(shè)置特定的屬性,,請(qǐng)繼續(xù)往下看。 2.2 使用Task實(shí)例實(shí)現(xiàn)多線(xiàn)程//簡(jiǎn)單的Task實(shí)例創(chuàng)建線(xiàn)程Action<object> action = (object obj) =>{ Console.WriteLine('Task={0}, obj={1}, Thread={2}', Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId);};//上面的是簡(jiǎn)寫(xiě)形式,也可以寫(xiě)成下面的形式.//Action<object> action = new Action<object>((object obj) =>//{// Console.WriteLine('Task={0}, obj={1}, Thread={2}', Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId);//});Task t1 = new Task(action, '參數(shù)');t1.Start(); 運(yùn)行結(jié)果如下: //簡(jiǎn)寫(xiě)上面實(shí)例,,并創(chuàng)建100個(gè)線(xiàn)程System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();int m = 100;Task[] tasks = new Task[m];for (int i = 0; i < m; i++){ tasks[i] = new Task((object obj) => { Thread.Sleep(200); Console.WriteLine('Task={0}, obj={1}, Thread={2},當(dāng)前時(shí)間:{3}', Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId, System.DateTime.Now.ToString()); }, '參數(shù)' + i.ToString() //public Task(Action<object> action, object state); ); tasks[i].Start();//線(xiàn)程開(kāi)始} Task.WaitAll(tasks); //等待提供的所有 System.Threading.Tasks.Task 對(duì)象完成執(zhí)行過(guò)程,。Console.WriteLine('線(xiàn)程耗時(shí):{0},當(dāng)前時(shí)間:{1}' ,watch.ElapsedMilliseconds,System.DateTime.Now.ToString()); 這里task創(chuàng)建的100個(gè)線(xiàn)程貌似是異步執(zhí)行的 運(yùn)行結(jié)果如下: 2.3 Task傳入?yún)?shù)上面介紹了使用一個(gè)Action委托來(lái)完成線(xiàn)程,,那么給線(xiàn)程中傳入?yún)?shù),就可以使用System.Action<object>來(lái)完成,。 傳入一個(gè)參數(shù)的示例: /// <summary>/// 一個(gè)參數(shù)的方法/// </summary>/// <param name='parameter'></param>static void MyMethod(string parameter){ Console.WriteLine('{0}', parameter);} 調(diào)用如下: //Task傳入一個(gè)參數(shù)Task myTask = new Task((parameter) => MyMethod(parameter.ToString()), 'aaa');myTask.Start(); 傳入多個(gè)參數(shù)如下: /// <summary>/// 多個(gè)參數(shù)的方法/// </summary>/// <param name='parameter1'></param>/// <param name='parameter2'></param>/// <param name='parameter3'></param>static void MyMethod(string parameter1,int parameter2,DateTime parameter3){ Console.WriteLine('{0} {1} {2}', parameter1,parameter2.ToString(),parameter3.ToString());} 調(diào)用如下: //Task傳入多個(gè)參數(shù)for (int i = 1; i <= 20; i++){ new Task(() => { MyMethod('我的線(xiàn)程', i, DateTime.Now); }).Start(); Thread.Sleep(200);} 運(yùn)行結(jié)果如下: 對(duì)于傳入多個(gè)參數(shù),,可以使用無(wú)參數(shù)委托包裝一個(gè)多參數(shù)的方法來(lái)完成。 2.4 Task的結(jié)果要獲取Task的結(jié)果,,在創(chuàng)建Task的時(shí)候,,就要采用Task<T>來(lái)實(shí)例化一個(gè)Task。 其中的T就是Task執(zhí)行完成之后返回結(jié)果的類(lèi)型,。 通過(guò)Task實(shí)例的Result屬性就可以獲取結(jié)果,。 System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();Task<int> myTask = new Task<int>(() =>//這里面泛型委托修飾符out表示協(xié)變{ int sum = 0; for (int i = 0; i < 10000; i++) sum += i; return sum;});myTask.Start(); Console.WriteLine('結(jié)果: {0} 耗時(shí):{1}', myTask.Result,watch.ElapsedMilliseconds); 這里task創(chuàng)建線(xiàn)程執(zhí)行完才會(huì)繼續(xù)執(zhí)行主線(xiàn)程 運(yùn)行結(jié)果如下: 使用Factory屬性來(lái)完成上面的示例: //使用Factory屬性創(chuàng)建System.Diagnostics.Stopwatch watchSecond = System.Diagnostics.Stopwatch.StartNew();Task<int> myTaskSecond = Task.Factory.StartNew<int>(() =>{ int sum = 0; for (int i = 0; i < 10000; i++) sum += i; return sum;}); Console.WriteLine('結(jié)果: {0} 耗時(shí):{1}', myTaskSecond.Result, watchSecond.ElapsedMilliseconds); 這里task創(chuàng)建線(xiàn)程執(zhí)行完才會(huì)繼續(xù)執(zhí)行主線(xiàn)程 運(yùn)行結(jié)果如下: 多線(xiàn)程除以上的一些基礎(chǔ)知識(shí),在處理各種并行任務(wù)和多核編程中的使用,小伙伴可以參考專(zhuān)門(mén)關(guān)于多線(xiàn)程的書(shū)籍學(xué)習(xí),。 想要完全深入的學(xué)習(xí)多線(xiàn)程需要慢慢修煉,,不斷積累。 3. 本節(jié)要點(diǎn):A.本點(diǎn)簡(jiǎn)單介紹了線(xiàn)程池ThreadPool的使用,; B.介紹一使用Task進(jìn)行多線(xiàn)程創(chuàng)建及Tast的參數(shù)傳入和返回結(jié)果,。 線(xiàn)程一些小知識(shí)點(diǎn) 并發(fā)與并行例子:當(dāng)你要吃飯又要玩游戲 順序執(zhí)行:先吃完飯?jiān)偻嬗螒?br> 并發(fā):吃口飯玩一會(huì)游戲 并行:邊吃飯邊玩游戲 異步 同步 Thread ThreadPool和Task ThreadPool是Thread基礎(chǔ)上的一個(gè)線(xiàn)程池,,目的是減少頻繁創(chuàng)建線(xiàn)程的開(kāi)銷(xiāo)。線(xiàn)程很貴,,要開(kāi)新的stack,,要增加CPU上下文切換,所以ThreadPool適合頻繁,、短期執(zhí)行的小操作,。調(diào)度算法是自適應(yīng)的,會(huì)根據(jù)程序執(zhí)行的模式調(diào)整配置,,通常不需要自己調(diào)度線(xiàn)程,。另外分為Worker和IO兩個(gè)池。 Task或者說(shuō)TPL是一個(gè)更上層的封裝,,NB之處在于continuation,。continuation的意義在于:高性能的程序通常都是跑在IO邊界或者UI事件的邊界上的,TPL的continuation可以更方便的寫(xiě)這種高scalability的代碼,。Task會(huì)根據(jù)一些flag,,比如是不是long-running來(lái)決定底層用Thread還是ThreadPool 結(jié)論:能用Task就用Task,底下都是用的Thread或者ThreadPool。但是要注意細(xì)節(jié),,比如告訴Task是不是long-running,;比如盡量別Wait;再比如IO之后的continuation要盡快結(jié)束然后把線(xiàn)程還回去,,有事開(kāi)個(gè)Worker做,,要不然會(huì)影響后面的IO,等等,。 |
|
來(lái)自: 吳敬銳 > 《待分類(lèi)》