keep moving!

          We must not cease from exploration. And the end of all our exploring will be to arrive where we began and to know the place for the first time.
          隨筆 - 37, 文章 - 2, 評論 - 3, 引用 - 0
          數(shù)據(jù)加載中……

          線程池的介紹及簡單實現(xiàn)

          線程池的介紹及簡單實現(xiàn)

          服務(wù)器程序利用線程技術(shù)響應(yīng)客戶請求已經(jīng)司空見慣,可能您認為這樣做效率已經(jīng)很高,但您有沒有想過優(yōu)化一下使用線程的方法。該文章將向您介紹服務(wù)器程序如何利用線程池來優(yōu)化性能并提供一個簡單的線程池實現(xiàn)。

          線程池的技術(shù)背景

          在面向?qū)ο缶幊讨校瑒?chuàng)建和銷毀對象是很費時間的,因為創(chuàng)建一個對象要獲取內(nèi)存資源或者其它更多資源。在Java中更是如此,虛擬機將試圖跟蹤每一個對象,以便能夠在對象銷毀后進行垃圾回收。所以提高服務(wù)程序效率的一個手段就是盡可能減少創(chuàng)建和銷毀對象的次數(shù),特別是一些很耗資源的對象創(chuàng)建和銷毀。如何利用已有對象來服務(wù)就是一個需要解決的關(guān)鍵問題,其實這就是一些"池化資源"技術(shù)產(chǎn)生的原因。比如大家所熟悉的數(shù)據(jù)庫連接池正是遵循這一思想而產(chǎn)生的,本文將介紹的線程池技術(shù)同樣符合這一思想。

          目前,一些著名的大公司都特別看好這項技術(shù),并早已經(jīng)在他們的產(chǎn)品中應(yīng)用該技術(shù)。比如IBM的WebSphere,IONA的Orbix 2000在SUN的 Jini中,Microsoft的MTS(Microsoft Transaction Server 2.0),COM+等。

          現(xiàn)在您是否也想在服務(wù)器程序應(yīng)用該項技術(shù)?


          線程池技術(shù)如何提高服務(wù)器程序的性能

          我所提到服務(wù)器程序是指能夠接受客戶請求并能處理請求的程序,而不只是指那些接受網(wǎng)絡(luò)客戶請求的網(wǎng)絡(luò)服務(wù)器程序。

          多線程技術(shù)主要解決處理器單元內(nèi)多個線程執(zhí)行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。但如果對多線程應(yīng)用不當,會增加對單個任務(wù)的處理時間。可以舉一個簡單的例子:

          假設(shè)在一臺服務(wù)器完成一項任務(wù)的時間為T

               T1 創(chuàng)建線程的時間
                T2 在線程中執(zhí)行任務(wù)的時間,包括線程間同步所需時間
                T3 線程銷毀的時間		

          顯然T = T1+T2+T3。注意這是一個極度簡化的假設(shè)。

          可以看出T1,T3是多線程本身的帶來的開銷,我們渴望減少T1,T3所用的時間,從而減少T的時間。但一些線程的使用者并沒有注意到這一點,所以在程序中頻繁的創(chuàng)建或銷毀線程,這導(dǎo)致T1和T3在T中占有相當比例。顯然這是突出了線程的弱點(T1,T3),而不是優(yōu)點(并發(fā)性)。

          線程池技術(shù)正是關(guān)注如何縮短或調(diào)整T1,T3時間的技術(shù),從而提高服務(wù)器程序性能的。它把T1,T3分別安排在服務(wù)器程序的啟動和結(jié)束的時間段或者一些空閑的時間段,這樣在服務(wù)器程序處理客戶請求時,不會有T1,T3的開銷了。

          線程池不僅調(diào)整T1,T3產(chǎn)生的時間段,而且它還顯著減少了創(chuàng)建線程的數(shù)目。在看一個例子:

          假設(shè)一個服務(wù)器一天要處理50000個請求,并且每個請求需要一個單獨的線程完成。我們比較利用線程池技術(shù)和不利于線程池技術(shù)的服務(wù)器處理這些請求時所產(chǎn)生的線程總數(shù)。在線程池中,線程數(shù)一般是固定的,所以產(chǎn)生線程總數(shù)不會超過線程池中線程的數(shù)目或者上限(以下簡稱線程池尺寸),而如果服務(wù)器不利用線程池來處理這些請求則線程總數(shù)為50000。一般線程池尺寸是遠小于50000。所以利用線程池的服務(wù)器程序不會為了創(chuàng)建50000而在處理請求時浪費時間,從而提高效率。

          這些都是假設(shè),不能充分說明問題,下面我將討論線程池的簡單實現(xiàn)并對該程序進行對比測試,以說明線程技術(shù)優(yōu)點及應(yīng)用領(lǐng)域。



          線程池的簡單實現(xiàn)及對比測試

          一般一個簡單線程池至少包含下列組成部分。

          1. 線程池管理器(ThreadPoolManager):用于創(chuàng)建并管理線程池
          2. 工作線程(WorkThread): 線程池中線程
          3. 任務(wù)接口(Task):每個任務(wù)必須實現(xiàn)的接口,以供工作線程調(diào)度任務(wù)的執(zhí)行。
          4. 任務(wù)隊列:用于存放沒有處理的任務(wù)。提供一種緩沖機制。

          線程池管理器至少有下列功能:創(chuàng)建線程池,銷毀線程池,添加新任務(wù)創(chuàng)建線程池的部分代碼如下:

          																				
          																						  …
                  //create threads
                  synchronized(workThreadVector)
                  {
                      for(int j = 0; j < i; j++)
                      {
                          threadNum++;
                         WorkThread workThread = new WorkThread(taskVector, threadNum);
                          workThreadVector.addElement(workThread);
                      }
          
                  }
          …
          
          																				
          																		

          注意同步workThreadVector并沒有降低效率,相反提高了效率,請參考Brian Goetz的文章。 銷毀線程池的部分代碼如下:

          																				
          																						  …
                  while(!workThreadVector.isEmpty())
                  {
                  if(debugLevel > 2)
                   System.out.println("stop:"+(i));
                   i++;
                      try
                      {
                          WorkThread workThread = (WorkThread)workThreadVector.remove(0);
                          workThread.closeThread();
                          continue;
                      }
                      catch(Exception exception)
                      {
                          if(debugLevel > 2)
                              exception.printStackTrace();
                      }
                      break;
                  }
             …
             
          																				
          																		

          添加新任務(wù)的部分代碼如下:

          																				
          																						   …
                  synchronized(taskVector)
                  {
                      taskVector.addElement(taskObj);
                      taskVector.notifyAll();
                  }
             …
             
          																				
          																		

          工作線程是一個可以循環(huán)執(zhí)行任務(wù)的線程,在沒有任務(wù)時將等待。由于代碼比較多在此不羅列.

          任務(wù)接口是為所有任務(wù)提供統(tǒng)一的接口,以便工作線程處理。任務(wù)接口主要規(guī)定了任務(wù)的入口,任務(wù)執(zhí)行完后的收尾工作,任務(wù)的執(zhí)行狀態(tài)等。在文章結(jié)尾有相關(guān)代碼的下載。

          以上所描述的線程池結(jié)構(gòu)很簡單,一些復(fù)雜的線程池結(jié)構(gòu)將不再此討論。

          在下載代碼中有測試驅(qū)動程序(TestThreadPool),我利用這個測試程序的輸出數(shù)據(jù)統(tǒng)計出下列測試結(jié)果。測試有兩個參數(shù)要設(shè)置:

          1. 線程池中線程數(shù),即線程池尺寸。
          2. 要完成的任務(wù)數(shù)。

          分別將一個參數(shù)固定,另一個參數(shù)變動以考察兩個參數(shù)所產(chǎn)生的不同結(jié)果。所用測試機器分別為普通PC機(Win2000 JDK1.3.1)和SUN服務(wù)器(Solaris Unix JDK1.3.1),機器配置在此不便指明。

          表1:測試數(shù)據(jù)及對應(yīng)結(jié)果

          線程池尺寸 任務(wù)數(shù) 沒有應(yīng)用線程池所用的時間(單位:毫秒,OS:win) 應(yīng)用線程池所用的時間(單位:毫秒,OS:win) 沒有應(yīng)用線程池所用的時間(單位:毫秒,OS:Solaris) 應(yīng)用線程池所用的時間(單位:毫秒,OS:Solaris)
          1 5000 3896 130 6513 327
          2 5000 3455 151 6221 659
          4 5000 3425 120 5448 433
          8 5000 3475 160 5769 1478
          16 5000 3505 211 5785 1970
          32 5000 3455 251 6403 875
          64 5000 3595 501 5182 1103
          128 5000 3515 881 5154 405
          256 5000 3495 3104 5502 1589
          512 5000 3425 5488 5667 1262
          16 1 20 0 22 3
          16 2 20 20 21 13
          16 4 20 10 27 10
          16 8 20 20 22 24
          16 16 30 20 29 48
          16 32 40 20 46 108
          16 64 60 20 72 199
          16 128 110 20 148 335
          16 256 201 20 252 132
          16 512 411 40 522 382
          16 1024 811 71 1233 610
          16 2048 1552 80 2045 135
          16 4096 2874 250 4828 787

          圖1.線程池的尺寸的對服務(wù)器程序的性能影響
          圖1.線程池的尺寸的對服務(wù)器程序的性能影響

          根據(jù)以上統(tǒng)計數(shù)據(jù)可得出下圖:


          圖2.任務(wù)數(shù)對服務(wù)器程序的沖擊
          圖2.任務(wù)數(shù)對服務(wù)器程序的沖擊

          數(shù)據(jù)分析如下:

          圖1是改變線程池尺寸對服務(wù)器性能的影響,在該測試過程中,服務(wù)器的要完成的任務(wù)數(shù)固定為為5000。從圖1中可以看出合理配置線程池尺寸對于大量任務(wù)處理的效率有非常明顯的提高,但是一旦尺寸選擇不合理(過大或過小)就會嚴重降低影響服務(wù)器性能。理論上"過小"將出現(xiàn)任務(wù)不能及時處理的情況,但在圖表中顯示出某些小尺寸的線程池表現(xiàn)很好,這是因為測試驅(qū)動中有很多線程同步開銷,且這個開銷相對于完成單個任務(wù)的時間是不能忽略的。"過大"則會出現(xiàn)線程間同步開銷太大的問題,而且在線程間切換很耗CPU時間,在圖表顯示的很清楚。可見任何一個好技術(shù),如果濫用都會造成災(zāi)難性后果。

          圖2是用不同數(shù)量的任務(wù)來沖擊服務(wù)器程序,在該測試過程中,服務(wù)器線程池尺寸固定為16。可以看出線程池在處理少量任務(wù)時的優(yōu)勢不明顯。所以線程池技術(shù)有一定的適應(yīng)范圍,關(guān)于適用范圍將在后面討論。但對于大量的任務(wù)的處理,線程池的優(yōu)勢表現(xiàn)非常卓越,服務(wù)器程序處理請求的時間雖然有波動,但是其平均值相對小多了。

          值得注意的是測試方案中,統(tǒng)計任務(wù)的完成時間沒有包含了創(chuàng)建線程池的時間。在實際線程池工作時,即利用線程池處理任務(wù)時,創(chuàng)建線程池的時間是不必計算在內(nèi)的。

          由于測試驅(qū)動程序有很多同步代碼,特別是等待線程執(zhí)行完畢的同步(代碼中為sleepToWait(long l)方法的調(diào)用),這些代碼降低了代碼執(zhí)行效率,這是測試驅(qū)動一個缺點,但這個測試驅(qū)動可以說明線程池相對于簡單使用線程的優(yōu)勢。



          關(guān)于高級線程池的探討

          簡單線程池存在一些問題,比如如果有大量的客戶要求服務(wù)器為其服務(wù),但由于線程池的工作線程是有限的,服務(wù)器只能為部分客戶服務(wù),其它客戶提交的任務(wù),只能在任務(wù)隊列中等待處理。一些系統(tǒng)設(shè)計人員可能會不滿這種狀況,因為他們對服務(wù)器程序的響應(yīng)時間要求比較嚴格,所以在系統(tǒng)設(shè)計時可能會懷疑線程池技術(shù)的可行性,但是線程池有相應(yīng)的解決方案。調(diào)整優(yōu)化線程池尺寸是高級線程池要解決的一個問題。主要有下列解決方案:

          方案一:動態(tài)增加工作線程

          在一些高級線程池中一般提供一個可以動態(tài)改變的工作線程數(shù)目的功能,以適應(yīng)突發(fā)性的請求。一旦請求變少了將逐步減少線程池中工作線程的數(shù)目。當然線程增加可以采用一種超前方式,即批量增加一批工作線程,而不是來一個請求才建立創(chuàng)建一個線程。批量創(chuàng)建是更加有效的方式。該方案還有應(yīng)該限制線程池中工作線程數(shù)目的上限和下限。否則這種靈活的方式也就變成一種錯誤的方式或者災(zāi)難,因為頻繁的創(chuàng)建線程或者短時間內(nèi)產(chǎn)生大量的線程將會背離使用線程池原始初衷--減少創(chuàng)建線程的次數(shù)。

          舉例:Jini中的TaskManager,就是一個精巧線程池管理器,它是動態(tài)增加工作線程的。SQL Server采用單進程(Single Process)多線程(Multi-Thread)的系統(tǒng)結(jié)構(gòu),1024個數(shù)量的線程池,動態(tài)線程分配,理論上限32767。

          方案二:優(yōu)化工作線程數(shù)目

          如果不想在線程池應(yīng)用復(fù)雜的策略來保證工作線程數(shù)滿足應(yīng)用的要求,你就要根據(jù)統(tǒng)計學的原理來統(tǒng)計客戶的請求數(shù)目,比如高峰時段平均一秒鐘內(nèi)有多少任務(wù)要求處理,并根據(jù)系統(tǒng)的承受能力及客戶的忍受能力來平衡估計一個合理的線程池尺寸。線程池的尺寸確實很難確定,所以有時干脆用經(jīng)驗值。

          舉例:在MTS中線程池的尺寸固定為100。

          方案三:一個服務(wù)器提供多個線程池

          在一些復(fù)雜的系統(tǒng)結(jié)構(gòu)會采用這個方案。這樣可以根據(jù)不同任務(wù)或者任務(wù)優(yōu)先級來采用不同線程池處理。

          舉例:COM+用到了多個線程池。

          這三種方案各有優(yōu)缺點。在不同應(yīng)用中可能采用不同的方案或者干脆組合這三種方案來解決實際問題。



          線程池技術(shù)適用范圍及應(yīng)注意的問題

          下面是我總結(jié)的一些線程池應(yīng)用范圍,可能是不全面的。

          線程池的應(yīng)用范圍:

          1. 需要大量的線程來完成任務(wù),且完成任務(wù)的時間比較短。 WEB服務(wù)器完成網(wǎng)頁請求這樣的任務(wù),使用線程池技術(shù)是非常合適的。因為單個任務(wù)小,而任務(wù)數(shù)量巨大,你可以想象一個熱門網(wǎng)站的點擊次數(shù)。 但對于長時間的任務(wù),比如一個Telnet連接請求,線程池的優(yōu)點就不明顯了。因為Telnet會話時間比線程的創(chuàng)建時間大多了。
          2. 對性能要求苛刻的應(yīng)用,比如要求服務(wù)器迅速相應(yīng)客戶請求。
          3. 接受突發(fā)性的大量請求,但不至于使服務(wù)器因此產(chǎn)生大量線程的應(yīng)用。突發(fā)性大量客戶請求,在沒有線程池情況下,將產(chǎn)生大量線程,雖然理論上大部分操作系統(tǒng)線程數(shù)目最大值不是問題,短時間內(nèi)產(chǎn)生大量線程可能使內(nèi)存到達極限,并出現(xiàn)"OutOfMemory"的錯誤。

          本文只是簡單介紹線程池技術(shù)。可以看出線程池技術(shù)對于服務(wù)器程序的性能改善是顯著的。線程池技術(shù)在服務(wù)器領(lǐng)域有著廣泛的應(yīng)用前景。希望這項技術(shù)能夠應(yīng)用到您的多線程服務(wù)程序中。



          張金鵬 2007-01-31 13:57 發(fā)表評論

          posted on 2008-09-07 11:10 大石頭 閱讀(171) 評論(0)  編輯  收藏 所屬分類: 多線程

          主站蜘蛛池模板: 沅陵县| 瑞安市| 甘南县| 泾源县| 武胜县| 义乌市| 比如县| 宁德市| 交城县| 方城县| 都兰县| 梧州市| 女性| 洛阳市| 楚雄市| 巴里| 新乐市| 丽水市| 泰宁县| 钟山县| 景东| 哈密市| 沁阳市| 高安市| 合肥市| 固镇县| 浮山县| 承德市| 枣庄市| 肥西县| 唐海县| 钦州市| 高碑店市| 溧阳市| 富平县| 九江县| 清镇市| 天台县| 和静县| 宁化县| 姚安县|