高效能是每個程式設計師的追求,無論寫一行**還是做乙個系統,都希望能夠達到高效能的效果。高效能架構設計主要集中在兩方面:
單伺服器高效能的關鍵之一就是伺服器採取的網路程式設計模型。伺服器如何管理連線,如何處理請求等。這兩個設計點最終都和作業系統的i/o模型及程序模型相關。
我們所說的i/o模型是指網路i/o模型,就是服務端如何管理連線,如何請求連線的措施,是用乙個程序管理乙個連線(ppc),還是乙個執行緒管理乙個連線(tpc),亦或者乙個程序管理多個連線(reactor)。
因此io多路復用中多路就是多個tcp連線(或多個channel),復用就是指復用乙個或少量執行緒,理解起來就是多個網路io復用乙個或少量執行緒來處理這些連線。
同步和非同步的概念描述的是使用者執行緒與核心的互動方式,這裡所說的使用者程序/執行緒和核心是以傳輸層為分割線的,傳輸層以上是指使用者程序,傳輸層以下(包括傳輸層)是指核心(處理所有通訊細節,傳送資料,等待確認,給無序到達的資料排序等,這四層是作業系統核心的一部分)。同步是指使用者執行緒發起io請求後需要等待或者輪詢核心io操作,完成後才能繼續執行。非同步是指使用者執行緒發起io請求後仍繼續執行,當核心io操作完成後回通知使用者執行緒,或者呼叫使用者執行緒註冊的**函式。
阻塞和非阻塞的概念描述的是使用者執行緒呼叫核心io操作的方式,阻塞時指io操作需要徹底完成後才能返回使用者空間,非阻塞時指io操作被呼叫後立即返回給使用者乙個狀態值,無需等待io操作徹底完成。
同步阻塞io是最簡單的io模型,使用者執行緒在核心進行io操作時被阻塞。使用者執行緒通過呼叫系統呼叫read發起io讀操作,由使用者空間轉到核心空間。核心等到資料報到達後,然後將接受的資料拷貝到使用者空間,完成read操作。整個io請求過程,使用者執行緒都是被阻塞的,對cpu利用率不夠
在同步基礎上,將socket設定為nonblock,這樣使用者執行緒可以在發起io請求後立即返回。雖說可以立即返回,但並未讀到任何資料,使用者執行緒需要不斷的發起io請求,直到資料到達後才能真正讀到資料,然後去處理。
整個io請求中,雖然可以立即返回,但是因為是同步的,為了等到資料,需要不斷的輪詢、重複請求,消耗了大量的cpu資源。因此,這種模型很少使用,實際用處不大。
不管是同步阻塞還是同步非阻塞,對系統效能的提公升都是很小的。而通過復用可以使乙個或一組執行緒(執行緒池)處理多個tcp連線。io多路復用使用兩個系統呼叫(select/poll/epoll和recvfrom),blocking io只呼叫了recvfrom。select/poll/epoll核心是可以同時處理多個connection,而不是更快,所以連線數不高的話,效能不一定比多執行緒+阻塞io好。
select是核心提供的多路分離函式,使用它可以避免同步非阻塞io中輪詢等待問題。
使用者首先將需要進行io操作的socket新增到select中,然後阻塞等待select系統呼叫返回。當資料到達時,socket被啟用,select函式返回,使用者執行緒正式發起read請求,讀取資料並繼續執行。
這麼一看,這種方式和同步阻塞io並沒有太大區別,甚至還多了新增監視socket以及呼叫select函式的額外操作,效率更差。但是使用select以後,使用者可以在乙個執行緒內同時處理多個socket的io請求,這就是它的最大優勢。使用者可以註冊多個socket,然後不斷呼叫select讀取被啟用的socket,即可達到同乙個執行緒同時處理多個io請求的目的。而在同步阻塞模型中,必須通過多執行緒方式才能達到這個目的。所以io多路復用設計目的其實不是為了快,而是為了解決執行緒/程序數量過多對伺服器開銷造成的壓力。
select(socket); #向select註冊socket
while(true)
}}
雖然這種方式允許單執行緒內處理多個io請求,但是每個io請求的過程還是阻塞的(在select函式上阻塞),平均時間甚至比同步阻塞io模型還要長。如果使用者執行緒只註冊自己感興趣的socket,然後去做自己的事情,等到資料到來時在進行處理,則可以提高cpu利用率。
通過reactor方式,使用者執行緒輪詢io操作狀態的工作統一交給handle_events事件迴圈處理。使用者執行緒註冊事件處理器之後可以繼續執行做其他的工作(非同步),而reactor執行緒負責呼叫核心的select函式檢查socket狀態。當有socket被啟用時,則通知相應的使用者執行緒(或執行使用者執行緒的**函式),執行handel_envent進行資料的讀取、處理工作。
由於select函式是阻塞的,因此多路io復用模型就被稱為非同步阻塞io模型,這裡阻塞不是指socket。因為使用io多路復用時,socket都設定nonblock,不過不影響,因為使用者發起io請求時,資料已經到達了,使用者執行緒一定不會被阻塞。
io多路復用是最常用的io模型,但其非同步程度還不徹底,因為它使用了回阻塞執行緒的select系統呼叫。因此io多路復用只能稱為非同步阻塞io,而非真正的非同步io。
附:reactor設計模式
在io多路復用模型中,事件迴圈檔案控制代碼的狀態事件通知給使用者執行緒,由使用者執行緒自行讀取資料、處理資料。而非同步io中,當使用者執行緒收到通知時候,資料已經被核心讀取完畢,並放在了使用者執行緒指定的緩衝區內,核心在io完成後通知使用者執行緒直接使用就行了。因此這種模型需要作業系統更強的支援,把read操作從使用者執行緒轉移到了核心。
相比於io多路復用模型,非同步io並不十分常用,不少高效能併發服務程式使用io多路復用+多執行緒任務處理的架構基本可以滿足需求。不過最主要原因還是作業系統對非同步io的支援並非特別完善,更多的採用io多路復用模擬非同步io方式(io事件觸發時不直接通知使用者執行緒,而是將資料讀寫完畢後放到使用者指定的緩衝區)。
select,poll,epoll都是io多路復用的機制。i/o多路復用就是通過一種機制,乙個程序可以監視多個描述符(socket),一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程式進行相應的讀寫操作。雖說io多路復用被稱為非同步阻塞io,但select,poll,epoll本質上都是同步io,因為它們都需要在續寫事件就緒後自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而真正意義上的非同步io無需自己負責進行讀寫。
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select函式監視的檔案描述符有三類,readfds,writefds,exceptfds。呼叫後函式會阻塞,直到有描述符就緒(有資料讀、寫、或者有except),或者超時(timeout指定時間,如果立即返回設定null),函式返回。當select函式返回後,可以通過便利fdset,來找到就緒的描述符。
優點:良好的跨平台性。
缺點:單個程序能夠監視的檔案描述符的數量存在最大限制,在linux上為1024,可以通過修改巨集定義甚至重新編譯核心的方式提公升這一限制,但這樣會造成效率的降低。
int poll(struct poll *fds, unsigned int nfds, int timeout);
struct pollfd;
與select使用三個點陣圖來表示fdset,poll使用乙個pollfd的指標實現。pollfd結構包含了要監視的event和發生的event,不在使用select引數傳值的方式。同時pollfd並沒有最大數量的限制(但數量過大效能也會下降)。和select一樣,poll返回後,需要輪詢pollfd來或許就緒的描述符。
epoll是select和poll的增強版本,相比於前兩者,它更加的靈活,沒有描述符的限制。epoll使用乙個檔案描述符管理多個描述符,將使用者關係的檔案描述符的事件存放到核心的乙個事件表中,這樣在使用者空間和核心空間的copy只需要一次。
《架構修煉之道》
《從零開始學架構》
I O多路復用技術(multiplexing)
首先,要從你常用的io操作談起,比如read和write,通常io操作都是阻塞i o的,也就是說當你呼叫read時,如果沒有資料收到,那麼執行緒或者程序就會被掛起,直到收到資料。l 這樣,當伺服器需要處理1000個連線的的時候,而且只有很少連線忙碌的,那麼會需要1000個執行緒或程序來處理1000個...
IO多路復用技術詳解
io多路復用 i o是指網路i o,多路指多個tcp連線 即socket或者channel 復用指復用乙個或幾個執行緒。意思說乙個或一組執行緒處理多個tcp連線。最大優勢是減少系統開銷小,不必建立過多的程序 執行緒,也不必維護這些程序 執行緒。io多路復用使用兩個系統呼叫 select poll e...
IO多路復用大揭秘 徹底搞懂poll函式
對於poll函式工作過程的理解 poll函式工作原理與select函式類似,也是監管一系列的檔案描述符,看這些檔案描述符是否可讀 可寫 異常,再去呼叫io函式讀寫 不過poll函式沒有監管檔案描述符個數的限制 與 select 在本質上沒有多大差別,管理多個描述符也是進行輪詢,根據描述符的狀態進行處...