1、池的概念
一般來說,服務器的硬件資源相對充裕,很多時候我們使用以空間換時間的方法來提高服務器的性能,不惜浪費更多的空間以換取服務器運行效率。具體做法是提前保存大量的資源,以備不時之需以及重復使用。這就是池的概念。池是一組資源的集合,這組資源在服務器啟動之初就已經被創建並初始化,這稱為靜態資源分配。當服務器正式運行,開始處理客戶請求的時候,如果需要相關的資源,服務器就可以直接從池中獲取,無需動態分配。動態分配即由系統實時分配資源,而右系統調用分配資源都是很耗時的。所以直接從池中取得資源比動態分配資源的效率更高。而且當服務器使用完資源后,可以直接放回資源池無需執行系統調用來釋放資源。池相當於服務器系統調用管理資源,避免了服務器對內核的頻繁訪問,從而提高服務器性能。
由於在實際應用當中,分配內存、創建進程、線程都會設計到一些系統調用,系統調用需要程序從用戶態切換到內核態,是非常耗時的操作。因此,當程序中需要頻繁的進行內存申請釋放,進程、線程創建銷毀等操作時,通常會使用內存池、進程池、線程池技術來提升程序的性能。
2、內存池
在C/C++中我們一般運用new或malloc向申請系統分配資源,由於所申請內存塊的大小不定,當頻繁使用時會造成大量的內存碎片,不僅耗時,而且大量內存碎片出現的結果是出現系統明明有大量資源而無法使用。如下圖:
如果我們再需要30字節資源的話,對於左邊有內存碎片問題的情況是無法得到30字節完整內存的,然而內存總共確實還有多余30字節的空間,這就是內存碎片問題。
內存池是指程序預先從操作系統申請一塊足夠大內存,此后,當程序中需要申請內存的時候,不是直接向操作系統申請,而是直接從內存池中獲取;同理,當程序釋放內存的時候,並不真正將內存返回給操作系統,而是返回內存池。當程序退出(或者特定時間)時,內存池才將之前申請的真正內存釋放。這樣做的一個顯著優點是盡量避免了內存碎片問題,還使得內存分配效率得到提升。
3、進程池與線程池
這兩個概念相似,這里以線程池為例介紹。
為什么要提出進程池線程池,因為它們可以解決服務器動態創建子進程(或子線程)的以下缺點:
(1)動態創建進程(或線程)比較耗費時間,這將導致較慢的客戶響應。
(2) 動態創建進程(或線程)將導致系統上產生大垃的細微進程(或線程)。進程(或線程)間的切換將消耗大量 CPU 時間。
(3)動態創建的子進程是當前進程的完整映像。當前進程必須遞慎地處理其分配的文件描述符和堆內存等系統資源,否則子進程可能復制這些資源,從而使系統的可用資源急劇下降,進而影響服務器的性能。
而進程池解決了以上問題。進程池是由服務器預先創建的一組子進程,這些子進程的數目在 3~10 個之間(當然這只是典型情況)。線程池中的線程數量應該和 CPU 數量差不多。進程池中的所有子進程都運行着相同的代碼,並具有相同的屬性,比如優先級、 PGID 等。因為進程池在服務器啟動之初就創建好了,所以每個子進程都相對 “ 干凈 ” ,即它們沒有打開不必要的文件描述符(從父進程繼承而來),也不會錯誤地使用大塊的堆內存(從父進程復制得到)。當有新的任務來到時,主進程將通過某種方式選擇進程池中的某一個子進程來為之服務。相比於動態創建子進程,選擇一個已經存在的子進程的代價顯得小得多。至於主進程選擇哪個子進程來為新任務服務,則有兩種方法:
(1)主進程使用某種算法來主動選擇子進程。最簡單、最常用的算法是隨機算法和 Round Robin 算法(輪流選取)。
(2)主進程和所有子進程通過一個共享的工作隊列來同步,子進程都睡眠在該工作隊列上。當有新的任務到來時,主進程將任務添加到工作隊列中。這將喚醒正在等待任務的子進程,不過只有一個子進程將獲得新任務的“接管權”,它可以從工作隊列中取出任務並執行之,而其他子進程將繼續睡眠在工作隊列上。
當選擇好子進程后,主進程還需要使用某種通知機制來告訴目標子進程有新任務需要處理,並傳遞必要的數據。最簡單的方式是,在父進程和子進程之間預先建立好一條管道,然后通過管道來實現所有的進程間通信。在父線程和子線程之間傳遞數據就要簡單得多,因為我們可以把這些數據定義為全局,那么它們本身就是被所有線程共享的。線程池主要用於:
(1)需要大量的線程來完成任務,且完成任務的時間比較短。 比如WEB服務器完成網頁請求這樣的任務,使用線程池技術是非常合適的。因為單個任務小,而任務數量巨大。但對於長時間的任務,比如一個Telnet連接請求,線程池的優點就不明顯了。因為Telnet會話時間比線程的創建時間大多了。
(2)對性能要求苛刻的應用,比如要求服務器迅速響應客戶請求。
(3)接受突發性的大量請求,但不至於使服務器因此產生大量線程的應用。
參考:《Linux高性能服務器》