Web Server 架構淺談-Threadpool-based Multiple Threaded Achitecture

上節我們講到了簡單的多線程架構,這個架構可以做一些改進和優化:

首先,是優化線程創建的開銷。操作系統默認的進程初始棧空間,32位操作系統爲1M,64位操作系統爲2M(不同操作系統版本可能會有差異)。那麼併發10K的線程可能需要10G內存,這是不可想象的,因此可以自行設定棧的大小和溢出區,代碼如下:

size_t size = max(10*PAGE_SIZE,PTHREAD_STACK_MIN);

void* base = get_from_penny_mmap(size);//從Mmap分配的虛擬內存中割一塊

ret = pthread_attr_setstack(&tattr, base,size);

 

假定我們的線程大部分情況下只需要1個Page的棧空間,我們用mmap的方式分配到的虛擬內存做自定義線程棧,合計10個Page,另外9個Page看做是溢出區,如果線程的棧沒有漲過1個Page,那麼着9個Page只是虛擬頁,不會調實際物理內存頁,因此可以看做是無開銷,萬一溢出了,只是多一個調頁過程。

其次,一個client通常是短連接的,即便是keep-alive的形式,每用戶創建一個線程的代價還是太高,是否可以讓線程的創建保持在一個常量呢?這就是線程池的思想,下面我們來看基於線程池的多線程架構。

基於線程池的多線程架構:

下圖是線程池的架構,系統在剛創建時,創建有限個線程,這些線程的生死都是隨着系統的創建和退出相聯繫,和用戶訪問無關。在獲得鏈接請求後,將用戶的請求看做是一個消息,將其插入到請求隊列中,線程池的dispatch loop不斷去將這個消息派發給一個閒置的線程進行處理,線程在處理完後進入等待隊列,等待dispatch loop派發任務。


dispatch loop是可以去掉的,每個處理線程在處理完任務後,直接去request queue中取任務,但這樣有幾個缺點:1)對request的隊頭訪問頻繁;2)當線程池線程不夠時無法自我感知已增加新線程。因此在每個處理線程之外,需要一個承擔管理職責的線程。

 

在每個線程處理完任務後,進入一個等待信號的過程,可以採用掛起和spin的方式,前者會有上下文切換的開銷,後者會出現循環空轉的開銷,dispatch在隊頭取得的線程等待信號,給予激活同時賦予請求的任務。在具體實現上還會有多種變化,這裏不一一介紹,

 

基於線程池的多線程架構在併發線程的數量上大大減少,上下文切換相應減少;線程創建的數量與客戶請求無關,創建成本減少;線程池的資源爭用情況大大減少,資源飽和使用的程度大大加強,因此線程池在吞吐率(throughput,單位時間交互的數據量)上通常較高。但付出了排隊的成本,使得響應時間會大大提高,這是線程池架構主要的問題。

 

我們日常生活中在銀行辦理業務,就可以看做是線程池模型的例子,排隊機就是request queue,叫號系統就是dispatch loop,自動將到號的用戶分配給一個空閒的客服,客服就是每個處理線程,客服在處理完業務後,按鍵進入閒狀態,等待叫號系統安排。銀行的這種設置顯然是基於吞吐率考慮的,而不是響應時間考慮的,和簡單多線程方式那種資源搶佔式的無序相比,基於線程池的排隊處理看上去更加優化。


發佈了2 篇原創文章 · 獲贊 2 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章