自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

淺談CLR線(xiàn)程池的缺點(diǎn)及解決方法

開(kāi)發(fā) 后端
關(guān)于CLR線(xiàn)程池的基本概念我們還不太清楚,這里筆者再來(lái)補(bǔ)充一些必要的信息,有助于我們?cè)诔绦蛑羞x擇合適的使用方式。這里將談到獨(dú)立線(xiàn)程池與IO線(xiàn)程池。

獨(dú)立線(xiàn)程池

上次我們?cè)?A target=_blank>CLR線(xiàn)程池的作用與原理淺析一文中討論到,在一個(gè).NET應(yīng)用程序中會(huì)有一個(gè)CLR線(xiàn)程池,可以使用ThreadPool類(lèi)中的靜態(tài)方法來(lái)使用這個(gè)線(xiàn)程池。我們只要使用QueueUserWorkItem方法向線(xiàn)程池中添加任務(wù),線(xiàn)程池就會(huì)負(fù)責(zé)在合適的時(shí)候執(zhí)行它們。我們還討論了CLR線(xiàn)程池的一些高級(jí)特性,例如對(duì)線(xiàn)程的最大和最小數(shù)量作限制,對(duì)線(xiàn)程創(chuàng)建時(shí)間作限制以避免突發(fā)的大量任務(wù)消耗太多資源等等。

那么.NET提供的線(xiàn)程池又有什么缺點(diǎn)呢?有些朋友說(shuō),一個(gè)重要的缺點(diǎn)就是功能太簡(jiǎn)單,例如只有一個(gè)隊(duì)列,沒(méi)法做到對(duì)多個(gè)隊(duì)列作輪詢(xún),無(wú)法取消任務(wù),無(wú)法設(shè)定任務(wù)優(yōu)先級(jí),無(wú)法限制任務(wù)執(zhí)行速度等等。不過(guò)其實(shí)這些簡(jiǎn)單的功能,倒都可以通過(guò)在CLR線(xiàn)程池上增加一層(或者說(shuō),通過(guò)封裝CLR線(xiàn)程池)來(lái)實(shí)現(xiàn)。例如,您可以讓放入CLR線(xiàn)程池中的任務(wù),在執(zhí)行時(shí)從幾個(gè)自定義任務(wù)隊(duì)列中挑選一個(gè)運(yùn)行,這樣便達(dá)到了對(duì)多個(gè)隊(duì)列作輪詢(xún)的效果。因此,在我看來(lái),CLR線(xiàn)程池的主要缺點(diǎn)并不在此。

我認(rèn)為,CLR線(xiàn)程池的主要問(wèn)題在于“大一統(tǒng)”,也就是說(shuō),整個(gè)進(jìn)程內(nèi)部幾乎所有的任務(wù)都會(huì)依賴(lài)這個(gè)線(xiàn)程池。如前篇文章所說(shuō)的那樣,如Timer和WaitForSingleObject,還有委托的異步調(diào)用,.NET框架中的許多功能都依賴(lài)這個(gè)線(xiàn)程池。這個(gè)做法是合適的,但是由于開(kāi)發(fā)人員對(duì)于統(tǒng)一的線(xiàn)程池?zé)o法做到精確控制,因此在一些特別的需要就無(wú)法滿(mǎn)足了。舉個(gè)最常見(jiàn)例子:控制運(yùn)算能力。什么是運(yùn)算能力?那么還是從線(xiàn)程講起吧。

我們?cè)谝粋€(gè)程序中創(chuàng)建一個(gè)線(xiàn)程,安排給它一個(gè)任務(wù),便交由操作系統(tǒng)來(lái)調(diào)度執(zhí)行。操作系統(tǒng)會(huì)管理系統(tǒng)中所有的線(xiàn)程,并且使用一定的方式進(jìn)行調(diào)度。什么是“調(diào)度”?調(diào)度便是控制線(xiàn)程的狀態(tài):執(zhí)行,等待等等。我們都知道,從理論上來(lái)說(shuō)有多少個(gè)處理單元(如2 * 2 CPU的機(jī)器便有4個(gè)處理單元),就表示操作系統(tǒng)可以同時(shí)做幾件事情。但是線(xiàn)程的數(shù)量會(huì)遠(yuǎn)遠(yuǎn)超過(guò)處理單元的數(shù)量,因此操作系統(tǒng)為了保證每個(gè)線(xiàn)程都被執(zhí)行,就必須等一個(gè)線(xiàn)程在某個(gè)處理器上執(zhí)行到某個(gè)情況的時(shí)候,“換”一個(gè)新的線(xiàn)程來(lái)執(zhí)行,這便是所謂的“上下文切換(context switch)”。至于造成上下文切換的原因也有多種,可能是某個(gè)線(xiàn)程的邏輯決定的,如遇上鎖,或主動(dòng)進(jìn)入休眠狀態(tài)(調(diào)用Thread.Sleep方法),但更有可能是操作系統(tǒng)發(fā)現(xiàn)這個(gè)線(xiàn)程“超時(shí)”了。在操作系統(tǒng)中會(huì)定義一個(gè)“時(shí)間片(timeslice)”2,當(dāng)發(fā)現(xiàn)一個(gè)線(xiàn)程執(zhí)行時(shí)間超過(guò)這個(gè)時(shí)間,便會(huì)把它撤下,換上另外一個(gè)。這樣看起來(lái),多個(gè)線(xiàn)程——也就是多個(gè)任務(wù)在同時(shí)運(yùn)行了。值得一提的是,對(duì)于Windows操作系統(tǒng)來(lái)說(shuō),它的調(diào)度單元是線(xiàn)程,這和線(xiàn)程究竟屬于哪個(gè)進(jìn)程并沒(méi)有關(guān)系。

舉個(gè)例子,如果系統(tǒng)中只有兩個(gè)進(jìn)程,進(jìn)程A有5個(gè)線(xiàn)程,而進(jìn)程B有10個(gè)線(xiàn)程。在排除其他因素的情況下,進(jìn)程B占有運(yùn)算單元的時(shí)間便是進(jìn)程A的兩倍。當(dāng)然,實(shí)際情況自然不會(huì)那么簡(jiǎn)單。例如不同進(jìn)程會(huì)有不同的優(yōu)先級(jí),線(xiàn)程相對(duì)于自己所屬的進(jìn)程還會(huì)有個(gè)優(yōu)先級(jí);如果一個(gè)線(xiàn)程在許久沒(méi)有執(zhí)行的時(shí)候,或者這個(gè)線(xiàn)程剛從“鎖”的等待中恢復(fù),操作系統(tǒng)還會(huì)對(duì)這個(gè)線(xiàn)程的優(yōu)先級(jí)作臨時(shí)的提升——這一切都是牽涉到程序的運(yùn)行狀態(tài),性能等情況的因素,有機(jī)會(huì)我們?cè)谧稣归_(kāi)。

現(xiàn)在您意識(shí)到線(xiàn)程數(shù)量意味著什么了沒(méi)?沒(méi)錯(cuò),就是我們剛才提到的“運(yùn)算能力”。很多時(shí)候我們可以簡(jiǎn)單的認(rèn)為,在同樣的環(huán)境下,一個(gè)任務(wù)使用的線(xiàn)程數(shù)量越多,它所獲得的運(yùn)算能力就比另一個(gè)線(xiàn)程數(shù)量較少的任務(wù)要來(lái)得多。運(yùn)算能力自然就涉及到任務(wù)執(zhí)行的快慢。您可以設(shè)想一下,有一個(gè)生產(chǎn)任務(wù),和一個(gè)消費(fèi)任務(wù),它們使用一個(gè)隊(duì)列做臨時(shí)存儲(chǔ)。在理想情況下,生產(chǎn)和消費(fèi)的速度應(yīng)該保持相同,這樣可以帶來(lái)最好的吞吐量。如果生產(chǎn)任務(wù)執(zhí)行較快,則隊(duì)列中便會(huì)產(chǎn)生堆積,反之消費(fèi)任務(wù)就會(huì)不斷等待,吞吐量也會(huì)下降。因此,在實(shí)現(xiàn)的時(shí)候,我們往往會(huì)為生產(chǎn)任務(wù)和消費(fèi)任務(wù)分別指派獨(dú)立的線(xiàn)程池,并且通過(guò)增加或減少線(xiàn)程池內(nèi)線(xiàn)程數(shù)量來(lái)?xiàng)l件運(yùn)算能力,使生產(chǎn)和消費(fèi)的步調(diào)達(dá)到平衡。

使用獨(dú)立的線(xiàn)程池來(lái)控制運(yùn)算能力的做法很常見(jiàn),一個(gè)典型的案例便是SEDA架構(gòu):整個(gè)架構(gòu)由多個(gè)Stage連接而成,每個(gè)Stage均由一個(gè)隊(duì)列和一個(gè)獨(dú)立的線(xiàn)程池組成,調(diào)節(jié)器會(huì)根據(jù)隊(duì)列中任務(wù)的數(shù)量來(lái)調(diào)節(jié)線(xiàn)程池內(nèi)的線(xiàn)程數(shù)量,最終使應(yīng)用程序獲得優(yōu)異的并發(fā)能力。

在Windows操作系統(tǒng)中,Server 2003及之前版本的API也只提供了進(jìn)程內(nèi)部單一的線(xiàn)程池,不過(guò)在Vista及Server 2008的API中,除了改進(jìn)線(xiàn)程池的性能之外,還提供了在同一進(jìn)程內(nèi)創(chuàng)建多個(gè)線(xiàn)程池的接口。很可惜,.NET直到如今的4.0版本,依舊沒(méi)有提供構(gòu)建獨(dú)立線(xiàn)程池的功能。構(gòu)造一個(gè)優(yōu)秀的線(xiàn)程池是一件相當(dāng)困難的事情,幸運(yùn)的是,如果我們需要這方面的功能,可以借助著名的SmartThreadPool,經(jīng)過(guò)那么多年的考驗(yàn),相信它已經(jīng)足夠成熟了。如果需要,我們還可以對(duì)它做一定修改——畢竟在不同情況下,我們對(duì)線(xiàn)程池的要求也不完全相同。

IO線(xiàn)程池

IO線(xiàn)程池便是為異步IO服務(wù)的線(xiàn)程池。

訪(fǎng)問(wèn)IO最簡(jiǎn)單的方式(如讀取一個(gè)文件)便是阻塞的,代碼會(huì)等待IO操作成功(或失?。┲蟛爬^續(xù)執(zhí)行下去,一切都是順序的。但是,阻塞式IO有很多缺點(diǎn),例如讓UI停止響應(yīng),造成上下文切換,CPU中的緩存也可能被清除甚至內(nèi)存被交換到磁盤(pán)中去,這些都是明顯影響性能的做法。此外,每個(gè)IO都占用一個(gè)線(xiàn)程,容易導(dǎo)致系統(tǒng)中線(xiàn)程數(shù)量很多,最終限制了應(yīng)用程序的伸縮性。因此,我們會(huì)使用“異步IO”這種做法。

在使用異步IO時(shí),訪(fǎng)問(wèn)IO的線(xiàn)程不會(huì)被阻塞,邏輯將會(huì)繼續(xù)下去。操作系統(tǒng)會(huì)負(fù)責(zé)把結(jié)果通過(guò)某種方法通知我們,一般說(shuō)來(lái),這種方式是“回調(diào)函數(shù)”。異步IO在執(zhí)行過(guò)程中是不占用應(yīng)用程序的線(xiàn)程的,因此我們可以用少量的線(xiàn)程發(fā)起大量的IO,所以應(yīng)用程序的響應(yīng)能力也可以有所提高。此外,同時(shí)發(fā)起大量IO操作在某些時(shí)候會(huì)有額外的性能優(yōu)勢(shì),例如磁盤(pán)和網(wǎng)絡(luò)可以同時(shí)工作而不互相沖突,磁盤(pán)還可以根據(jù)磁頭的位置來(lái)訪(fǎng)問(wèn)就近的數(shù)據(jù),而不是根據(jù)請(qǐng)求的順序進(jìn)行數(shù)據(jù)讀取,這樣可以有效減少磁頭的移動(dòng)距離。

Windows操作系統(tǒng)中有多種異步IO方式,但是性能最高,伸縮性最好的方式莫過(guò)于傳說(shuō)中的“IO完成端口(I/O Completion Port,IOCP)”了,這也是.NET中封裝的唯一異步IO方式。大約一年半前,老趙寫(xiě)過(guò)一篇文章《正確使用異步操作》,其中除了描述計(jì)算密集型和IO密集型操作的區(qū)別和效果之外,還簡(jiǎn)單地講述了IOCP與CLR交互的方式,摘錄如下:

當(dāng)我們希望進(jìn)行一個(gè)異步的IO-Bound Operation時(shí),CLR會(huì)(通過(guò)Windows API)發(fā)出一個(gè)IRP(I/O Request Packet)。當(dāng)設(shè)備準(zhǔn)備妥當(dāng),就會(huì)找出一個(gè)它“最想處理”的IRP(例如一個(gè)讀取離當(dāng)前磁頭最近的數(shù)據(jù)的請(qǐng)求)并進(jìn)行處理,處理完畢后設(shè)備將會(huì)(通過(guò)Windows)交還一個(gè)表示工作完成的IRP。CLR會(huì)為每個(gè)進(jìn)程創(chuàng)建一個(gè)IOCP(I/O Completion Port)并和Windows操作系統(tǒng)一起維護(hù)。IOCP中一旦被放入表示完成的IRP之后(通過(guò)內(nèi)部的ThreadPool.BindHandle完成),CLR就會(huì)盡快分配一個(gè)可用的線(xiàn)程用于繼續(xù)接下去的任務(wù)。
不過(guò)事實(shí)上,使用Windows API編寫(xiě)IOCP非常復(fù)雜。而在.NET中,由于需要迎合標(biāo)準(zhǔn)的APM(異步編程模型),在使用方便的同時(shí)也放棄一定的控制能力。因此,在一些真正需要高吞吐量的時(shí)候(如編寫(xiě)服務(wù)器),不少開(kāi)發(fā)人員還是會(huì)選擇直接使用Native Code編寫(xiě)相關(guān)代碼。不過(guò)在絕大部分的情況下,.NET中利用IOCP的異步IO操作已經(jīng)足以獲得非常優(yōu)秀的性能了。使用APM方式在.NET中使用異步IO非常簡(jiǎn)單,如下:

  1. static void Main(string[] args)  
  2. {  
  3.     WebRequest request = HttpWebRequest.Create("http://www.cnblogs.com");  
  4.     request.BeginGetResponse(HandleAsyncCallback, request);  
  5. }  
  6.  
  7. static void HandleAsyncCallback(IAsyncResult ar)  
  8. {  
  9.     WebRequest request = (WebRequest)ar.AsyncState;  
  10.     WebResponse response = request.EndGetResponse(ar);  
  11.     // more operations...  
  12. }

BeginGetResponse 將發(fā)起一個(gè)利用IOCP的異步IO操作,并在結(jié)束時(shí)調(diào)用HandleAsyncCallback回調(diào)函數(shù)。那么,這個(gè)回調(diào)函數(shù)是由哪里的線(xiàn)程執(zhí)行的呢?沒(méi)錯(cuò),就是傳說(shuō)中“IO線(xiàn)程池”的線(xiàn)程。.NET在一個(gè)進(jìn)程中準(zhǔn)備了兩個(gè)線(xiàn)程池,除了上篇文章中所提到的CLR線(xiàn)程池之外,它還為異步IO操作的回調(diào)準(zhǔn)備了一個(gè)IO線(xiàn)程池。IO線(xiàn)程池的特性與CLR線(xiàn)程池類(lèi)似,也會(huì)動(dòng)態(tài)地創(chuàng)建和銷(xiāo)毀線(xiàn)程,并且也擁有最大值和最小值(可以參考上一篇文章列舉出的API)。

只可惜,IO線(xiàn)程池也僅僅是那“一整個(gè)”線(xiàn)程池,CLR線(xiàn)程池的缺點(diǎn)IO線(xiàn)程池也一應(yīng)俱全。例如,在使用異步IO方式讀取了一段文本之后,下一步操作往往是對(duì)其進(jìn)行分析,這就進(jìn)入了計(jì)算密集型操作了。但對(duì)于計(jì)算密集型操作來(lái)說(shuō),如果使用整個(gè)IO線(xiàn)程池來(lái)執(zhí)行,我們無(wú)法有效的控制某項(xiàng)任務(wù)的運(yùn)算能力。因此在有些時(shí)候,我們?cè)诨卣{(diào)函數(shù)內(nèi)部會(huì)把計(jì)算任務(wù)再次交還給獨(dú)立的線(xiàn)程池。這么做從理論上看會(huì)增大線(xiàn)程調(diào)度的開(kāi)銷(xiāo),不過(guò)實(shí)際情況還得看具體的評(píng)測(cè)數(shù)據(jù)。如果它真的成為影響性能的關(guān)鍵因素之一,我們就可能需要使用Native Code來(lái)調(diào)用IOCP相關(guān)API,將回調(diào)任務(wù)直接交給獨(dú)立的線(xiàn)程池去執(zhí)行了。

我們也可以使用代碼來(lái)操作IO線(xiàn)程池,例如下面這個(gè)接口便是向IO線(xiàn)程池遞交一個(gè)任務(wù):

  1. public static class ThreadPool  
  2. {  
  3.     public static bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped);  

NativeOverlapped包含了一個(gè)IOCompletionCallback回調(diào)函數(shù)及一個(gè)緩沖對(duì)象,可以通過(guò)Overlapped對(duì)象創(chuàng)建

Overlapped會(huì)包含一個(gè)被固定的空間,這里“固定”的含義表示不會(huì)因?yàn)镚C而導(dǎo)致地址改變,甚至不會(huì)被置換到硬盤(pán)上的Swap空間去。這么做的目的是迎合IOCP的要求,但是很明顯它也會(huì)降低程序性能。因此,我們?cè)趯?shí)際編程中幾乎不會(huì)使用這個(gè)方法3。

注1:如果沒(méi)有加以說(shuō)明,我們這里談?wù)摰膶?duì)象默認(rèn)為XP及以上版本的Window操作系統(tǒng)。

注2:timeslice又被稱(chēng)為quantum,不同操作系統(tǒng)中定義的這個(gè)值并不相同。在Windows客戶(hù)端操作系統(tǒng)(XP,Vista)中時(shí)間片默認(rèn)為2個(gè)clock interval,在服務(wù)器操作系統(tǒng)(2003,2008)中默認(rèn)為12個(gè)clock interval(在主流系統(tǒng)上,1個(gè)clock interval大約10到15毫秒)。服務(wù)器操作系統(tǒng)使用較長(zhǎng)的時(shí)間片,是因?yàn)橐话惴?wù)器上運(yùn)行的程序比客戶(hù)端要少很多,且更注重性能和吞吐量,而客戶(hù)端系統(tǒng)更注重響應(yīng)能力——而且,如果您真需要的話(huà),時(shí)間片的長(zhǎng)度也是可以調(diào)整的。

注3:不過(guò),如果程序中多次復(fù)用單個(gè)NativeOverlapped對(duì)象的話(huà),這個(gè)方法的性能會(huì)略微好于QueueUserWorkItem,據(jù)說(shuō)WCF中便使用了這種方式——微軟內(nèi)部總有那么些技巧是我們不知如何使用的,例如老趙記得之前查看ASP.NET AJAX源代碼的時(shí)候,在MSDN中不小心發(fā)現(xiàn)一個(gè)接口描述大意是“預(yù)留方法,請(qǐng)不要在外部使用”。對(duì)此,我們又能有什么辦法呢?

【編輯推薦】

  1. CLR線(xiàn)程池的作用與原理淺析
  2. 一個(gè)非常簡(jiǎn)單和短小的線(xiàn)程池
  3. Java學(xué)習(xí):線(xiàn)程池的簡(jiǎn)單構(gòu)建
  4. 創(chuàng)建Java中的線(xiàn)程池
  5. 線(xiàn)程池與工作隊(duì)列
責(zé)任編輯:彭凡 來(lái)源: cnblogs
相關(guān)推薦

2009-07-03 18:14:27

Servlet線(xiàn)程安全

2009-07-22 09:39:18

CLR線(xiàn)程池

2009-06-17 15:33:50

java heap s

2010-07-22 14:05:33

krb5-telnet

2009-07-09 17:14:11

Incompatibl

2009-07-01 18:14:36

JSP亂碼

2023-10-26 08:16:20

C++線(xiàn)程

2009-07-10 14:32:06

JVM崩潰

2011-05-06 17:25:58

硒鼓

2012-05-15 02:18:31

Java線(xiàn)程池

2010-08-12 09:30:08

Flex內(nèi)存泄露

2022-04-02 20:27:30

ETS操作系統(tǒng)鴻蒙

2011-04-29 13:22:48

ThinkPad筆記本故障

2010-06-21 09:54:50

Linux Aplay

2011-12-02 14:00:21

JavaOOM

2016-09-23 20:46:53

2011-06-16 10:27:55

.NET內(nèi)存泄漏

2019-10-11 19:45:28

SparkSQLHiveHadoop

2010-12-27 10:48:10

VirtualboxFreedos

2022-04-06 10:09:17

云服務(wù)云計(jì)算
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)