Linux下简单线程池的实现
線程池的技術(shù)背景
? ?在面向?qū)ο缶幊讨?#xff0c;創(chuàng)建和銷毀對象是很費(fèi)時(shí)間的,因?yàn)?span style="color:rgb(0,0,255)">創(chuàng)建一個(gè)對象要獲取內(nèi)存資源或者其它更多資源。在Java中更是如此,虛擬機(jī)將試圖跟蹤每一個(gè)對象,以便能夠在對象銷毀后進(jìn)行垃圾回收。所以提高服務(wù)程序效率的一個(gè)手段就是盡可能減少創(chuàng)建和銷毀對象的次數(shù),特別是一些很耗資源的對象創(chuàng)建和銷毀。如何利用已有對象來服務(wù)(不止一個(gè)不同的任務(wù))就是一個(gè)需要解決的關(guān)鍵問題,其實(shí)這就是一些"池化資源"技術(shù)產(chǎn)生的原因。比如大家所熟悉的數(shù)據(jù)庫連接池正是遵循這一思想而產(chǎn)生的,本文將介紹的線程池技術(shù)同樣符合這一思想。
? ?目前,一些著名的大公司都特別看好這項(xiàng)技術(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)用該項(xiàng)技術(shù)?
?
線程池技術(shù)如何提高服務(wù)器程序的性能
? ?我所提到服務(wù)器程序是指能夠接受客戶請求并能處理請求的程序,而不只是指那些接受網(wǎng)絡(luò)客戶請求的網(wǎng)絡(luò)服務(wù)器程序。
? ?多線程技術(shù)主要解決處理器單元內(nèi)多個(gè)線程執(zhí)行的問題,它可以顯著減少處理器單元的閑置時(shí)間,增加處理器單元的吞吐能力。但如果對多線程應(yīng)用不當(dāng),會(huì)增加對單個(gè)任務(wù)的處理時(shí)間。可以舉一個(gè)簡單的例子:
?
假設(shè)在一臺(tái)服務(wù)器完成一項(xiàng)任務(wù)的時(shí)間為T
T1?創(chuàng)建線程的時(shí)間?????????????????????????????????
T2?在線程中執(zhí)行任務(wù)的時(shí)間,包括線程間同步所需時(shí)間?
T3?線程銷毀的時(shí)間 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
顯然T?=?T1+T2+T3。注意這是一個(gè)極度簡化的假設(shè)。
? ?可以看出T1,T3是多線程本身的帶來的開銷,我們渴望減少T1,T3所用的時(shí)間,從而減少T的時(shí)間。但一些線程的使用者并沒有注意到這一點(diǎn),所以在程序中頻繁的創(chuàng)建或銷毀線程,這導(dǎo)致T1和T3在T中占有相當(dāng)比例(在傳統(tǒng)的多線程服務(wù)器模型中是這樣實(shí)現(xiàn)的:一旦有個(gè)請求到達(dá),就創(chuàng)建一個(gè)新的線程,由該線程執(zhí)行任務(wù),任務(wù)執(zhí)行完畢之后,線程就退出。這就是"即時(shí)創(chuàng)建,即時(shí)銷毀"的策略。盡管與創(chuàng)建進(jìn)程相比,創(chuàng)建線程的時(shí)間已經(jīng)大大的縮短,但是如果提交給線程的任務(wù)是執(zhí)行時(shí)間較短,而且執(zhí)行次數(shù)非常頻繁,那么服務(wù)器就將處于一個(gè)不停的創(chuàng)建線程和銷毀線程的狀態(tài)。這筆開銷是不可忽略的,尤其是線程執(zhí)行的時(shí)間非常非常短的情況。)。顯然這是突出了線程的弱點(diǎn)(T1,T3),而不是優(yōu)點(diǎn)(并發(fā)性)。
? ?線程池技術(shù)正是關(guān)注如何縮短或調(diào)整T1,T3時(shí)間的技術(shù),從而提高服務(wù)器程序性能的。它把T1,T3分別安排在服務(wù)器程序的啟動(dòng)和結(jié)束的時(shí)間段或者一些空閑的時(shí)間段(在應(yīng)用程序啟動(dòng)之后,就馬上創(chuàng)建一定數(shù)量的線程,放入空閑的隊(duì)列中。這些線程都是處于阻塞狀態(tài),這些線程只占一點(diǎn)內(nèi)存,不占用CPU。當(dāng)任務(wù)到來后,線程池將選擇一個(gè)空閑的線程,將任務(wù)傳入此線程中運(yùn)行。當(dāng)所有的線程都處在處理任務(wù)的時(shí)候,線程池將自動(dòng)創(chuàng)建一定的數(shù)量的新線程,用于處理更多的任務(wù)。執(zhí)行任務(wù)完成之后線程并不退出,而是繼續(xù)在線程池中等待下一次任務(wù)。當(dāng)大部分線程處于阻塞狀態(tài)時(shí),線程池將自動(dòng)銷毀一部分的線程,回收系統(tǒng)資源),這樣在服務(wù)器程序處理客戶請求時(shí),不會(huì)有T1,T3的開銷了。
? ?線程池不僅調(diào)整T1,T3產(chǎn)生的時(shí)間段,而且它還顯著減少了創(chuàng)建線程的數(shù)目。再看一個(gè)例子:
? ?假設(shè)一個(gè)服務(wù)器一天要處理50000個(gè)請求,并且每個(gè)請求需要一個(gè)單獨(dú)的線程完成。我們比較利用線程池技術(shù)和不利于線程池技術(shù)的服務(wù)器處理這些請求時(shí)所產(chǎn)生的線程總數(shù)。在線程池中,線程數(shù)一般是固定的,所以產(chǎn)生線程總數(shù)不會(huì)超過線程池中線程的數(shù)目或者上限(以下簡稱線程池尺寸),而如果服務(wù)器不利用線程池來處理這些請求則線程總數(shù)為50000。一般線程池尺寸是遠(yuǎn)小于50000。所以利用線程池的服務(wù)器程序不會(huì)為了創(chuàng)建50000而在處理請求時(shí)浪費(fèi)時(shí)間,從而提高效率。
簡單線程池的實(shí)現(xiàn)
? ? ? ?1、線程池,顧名思義,就是要?jiǎng)?chuàng)建很多線程。創(chuàng)建線程的函數(shù)pthread_creat()應(yīng)該是最容易被想到的。有線程創(chuàng)建就要有線程退出pthread_exit(),在線程退出前,如果線程沒有設(shè)置pthread_detach()屬性,那么顯然要回收線程資源pthread_join()。當(dāng)然咯,可能要獲取線程的ID值pthread_self()。 ? 2、第一步創(chuàng)建了線程,剛開始線程是不做事情的,初始化好了,就等待。等待當(dāng)然不會(huì)是while(1)這種函數(shù),因?yàn)槟菢犹腃PU資源。容易想到的等待自然是使用條件變量的等待pthread_cond_wait(),這個(gè)函數(shù)干兩件事情,第一件是對解除與形參mutex對應(yīng)的互斥鎖,然后是重新加鎖,為的是在線程將任務(wù)放入任務(wù)隊(duì)列的一個(gè)緩沖。任務(wù)放入完成后,再加鎖,這樣不會(huì)影響其他任務(wù)獲取加鎖的權(quán)利。因此,在調(diào)用該函數(shù)之前,會(huì)自然會(huì)想到加互斥鎖。初始化互斥鎖函數(shù)pthread_mutex_init(),反初始化互斥鎖函數(shù)pthread_mutex_destroy(),加鎖函數(shù)pthread_mutex_lock(),解鎖函數(shù)pthread_mutex_unlock(),稍微再細(xì)化一點(diǎn),可能會(huì)用到嘗試解鎖pthread_mutex_trylock()。 ? ? ? ?3.、實(shí)現(xiàn)了上面二步,一個(gè)線程池的框架就初步搭起來了。當(dāng)然沒法用,因?yàn)檎嬲墒虑榈木€程全部在等待中,注意不應(yīng)該是超時(shí)的等待pthread_cond_timewait()。要使處于阻塞狀態(tài)的線程干事情,得用信號(hào)去喚醒它pthread_cond_signal(),“打鳥”的一個(gè)函數(shù),開一槍,總會(huì)把這只鳥吵醒,但具體是那一只,看那只最先在那排隊(duì)了(上面已經(jīng)說了pthread_cond_wait()函數(shù)的等待隊(duì)列問題)。當(dāng)然也可以想到“打鳥驚群”的函數(shù)pthread_cond_broadcast(),打一槍,無論打沒打著,一群鳥都飛走了。 ? 4、有了上面的基礎(chǔ),接下來就重點(diǎn)關(guān)注任務(wù)部分了。當(dāng)然線程數(shù)量有限,上面已經(jīng)說了,是固定的數(shù)目。因此任務(wù)大于線程數(shù)時(shí),排隊(duì)是難免的了。因此創(chuàng)建了一個(gè)任務(wù)隊(duì)列,隊(duì)列中的每一項(xiàng)代表一個(gè)任務(wù)。任務(wù)隊(duì)列的節(jié)點(diǎn)最簡單的模型就是一個(gè)處理任務(wù)的回掉函數(shù)void* (*callback_function)(void *arg)。指向函數(shù)的指針,參數(shù)是個(gè)指針,返回值也是個(gè)指針。具體的函數(shù)和參數(shù)則需要另外寫函數(shù)定義。沒次調(diào)用完線程處理完這個(gè)任務(wù),就需要把它從任務(wù)隊(duì)列中刪除。進(jìn)入任務(wù)隊(duì)列的任務(wù)數(shù)也不能無限多,因此也設(shè)為一個(gè)比線程數(shù)稍微大個(gè)幾個(gè)的一個(gè)固定值。 5、線程動(dòng)態(tài)創(chuàng)建:一個(gè)線程退出后(在任務(wù)執(zhí)行失敗時(shí),就有可能會(huì)退出),主線程要能檢測到,然后動(dòng)態(tài)創(chuàng)建一個(gè)新的線程,以維持線程池中線程總數(shù)不變??梢酝ㄟ^pthread_join()阻塞等待回收子線程資源,但是這就意味著主線程在阻塞狀態(tài)下干不了其他工作,因此考慮使用線程信號(hào),在子線程結(jié)束時(shí),給主線程用pthread_kill()發(fā)送一個(gè)SIGUSR1信號(hào),在主線程接收到此信號(hào)時(shí),通過調(diào)用注冊函數(shù)signal()或sigaction()函數(shù)注冊的函數(shù)創(chuàng)建一個(gè)新的線程。 ? 下面只列出Threadpool核心的實(shí)現(xiàn),封裝條件變量的類在這里沒有列出。 [cpp]?view plain?copy ?
[cpp]?view plain?copy ?
[cpp]?view plain?copy ?
[cpp]?view plain?copy ?
[cpp]?view plain?copy ?
關(guān)于高級(jí)線程池的探討
???簡單線程池存在一些問題,比如如果有大量的客戶要求服務(wù)器為其服務(wù),但由于線程池的工作線程是有限的,服務(wù)器只能為部分客戶服務(wù),其它客戶提交的任務(wù),只能在任務(wù)隊(duì)列中等待處理。一些系統(tǒng)設(shè)計(jì)人員可能會(huì)不滿這種狀況,因?yàn)樗麄儗Ψ?wù)器程序的響應(yīng)時(shí)間要求比較嚴(yán)格,所以在系統(tǒng)設(shè)計(jì)時(shí)可能會(huì)懷疑線程池技術(shù)的可行性,但是線程池有相應(yīng)的解決方案。調(diào)整優(yōu)化線程池尺寸是高級(jí)線程池要解決的一個(gè)問題。主要有下列解決方案:
?
方案一:動(dòng)態(tài)增加工作線程
? ?在一些高級(jí)線程池中一般提供一個(gè)可以動(dòng)態(tài)改變的工作線程數(shù)目的功能,以適應(yīng)突發(fā)性的請求。一旦請求變少了將逐步減少線程池中工作線程的數(shù)目。當(dāng)然線程增加可以采用一種超前方式,即批量增加一批工作線程,而不是來一個(gè)請求才建立創(chuàng)建一個(gè)線程。批量創(chuàng)建是更加有效的方式。該方案還有應(yīng)該限制線程池中工作線程數(shù)目的上限和下限。否則這種靈活的方式也就變成一種錯(cuò)誤的方式或者災(zāi)難,因?yàn)轭l繁的創(chuàng)建線程或者短時(shí)間內(nèi)產(chǎn)生大量的線程將會(huì)背離使用線程池原始初衷--減少創(chuàng)建線程的次數(shù)。
? ?舉例:Jini中的TaskManager,就是一個(gè)精巧線程池管理器,它是動(dòng)態(tài)增加工作線程的。SQL?Server采用單進(jìn)程(Single?Process)多線程(Multi-Thread)的系統(tǒng)結(jié)構(gòu),1024個(gè)數(shù)量的線程池,動(dòng)態(tài)線程分配,理論上限32767。
方案二:優(yōu)化工作線程數(shù)目
? ?如果不想在線程池應(yīng)用復(fù)雜的策略來保證工作線程數(shù)滿足應(yīng)用的要求,你就要根據(jù)統(tǒng)計(jì)學(xué)的原理來統(tǒng)計(jì)客戶的請求數(shù)目,比如高峰時(shí)段平均一秒鐘內(nèi)有多少任務(wù)要求處理,并根據(jù)系統(tǒng)的承受能力及客戶的忍受能力來平衡估計(jì)一個(gè)合理的線程池尺寸。線程池的尺寸確實(shí)很難確定,所以有時(shí)干脆用經(jīng)驗(yàn)值。
? ?舉例:在MTS中線程池的尺寸固定為100。?
方案三:一個(gè)服務(wù)器提供多個(gè)線程池
? ?在一些復(fù)雜的系統(tǒng)結(jié)構(gòu)會(huì)采用這個(gè)方案。這樣可以根據(jù)不同任務(wù)或者任務(wù)優(yōu)先級(jí)來采用不同線程池處理。
? ?舉例:COM+用到了多個(gè)線程池。
這三種方案各有優(yōu)缺點(diǎn)。在不同應(yīng)用中可能采用不同的方案或者干脆組合這三種方案來解決實(shí)際問題。
?
線程池技術(shù)適用范圍及應(yīng)注意的問題
下面是我總結(jié)的一些線程池應(yīng)用范圍,可能是不全面的。
線程池的應(yīng)用范圍:
? ?(1)需要大量的線程來完成任務(wù),且完成任務(wù)的時(shí)間比較短。?WEB服務(wù)器完成網(wǎng)頁請求這樣的任務(wù),使用線程池技術(shù)是非常合適的。因?yàn)閱蝹€(gè)任務(wù)小,而任務(wù)數(shù)量巨大,你可以想象一個(gè)熱門網(wǎng)站的點(diǎn)擊次數(shù)。?但對于長時(shí)間的任務(wù),比如一個(gè)Telnet連接請求,線程池的優(yōu)點(diǎn)就不明顯了。因?yàn)門elnet會(huì)話時(shí)間比線程的創(chuàng)建時(shí)間大多了。
? ?(2)對性能要求苛刻的應(yīng)用,比如要求服務(wù)器迅速相應(yīng)客戶請求。
? ?(3)接受突發(fā)性的大量請求,但不至于使服務(wù)器因此產(chǎn)生大量線程的應(yīng)用。突發(fā)性大量客戶請求,在沒有線程池情況下,將產(chǎn)生大量線程,雖然理論上大部分操作系統(tǒng)線程數(shù)目最大值不是問題,短時(shí)間內(nèi)產(chǎn)生大量線程可能使內(nèi)存到達(dá)極限,并出現(xiàn)"OutOfMemory"的錯(cuò)誤。
?
參考:
《UNP》
IBM文檔: http://www.ibm.com/developerworks/cn/java/l-threadPool/
http://blog.csdn.net/zjf280441589/article/details/43883137
原文地址:?http://blog.csdn.net/nk_test/article/details/50835550
總結(jié)
以上是生活随笔為你收集整理的Linux下简单线程池的实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 操作系统: 最佳适配算法和邻近适配算法的
- 下一篇: Linux内核中无名管道pipe和有名管