服務器端網絡編程之線程模型

  上一篇文章《服務器端網絡編程之 IO 模型》中講到服務器端高性能網絡編程的核心在於架構,而架構的核心在於進程-線程模型的選擇。本文將主要介紹傳統的和目前流行的進程-線程模型,在講進程-線程程模型以前須要先介紹一種設計模式: Reactor 模式,不明白的看這裏《設計模式詳解》,文中有一句話對 Reactor 模式總結的很好,引用下。html

  Reactor 模式首先是事件驅動的,有一個或多個併發輸入源,有一個Service Handler,有多個Request Handlers;這個Service Handler會同步的將輸入的請求(Event)多路複用的分發給相應的Request Handler。若是用圖表示的以下:java

  不知道讀者有沒有發現 Reactor 模式跟 IO 模型中的 IO 多路複用模型很是類似 ,在學習網絡編程過程當中也被這兩個概念迷惑了好久。其實在設計模式層面 IO 多路複用也是採用 Reactor 模式的。IO 多路複用模型能夠當作是 Reactor 模式在 IO 模型上的應用,而今天咱們要講的是 Reactor 模式在進程-線程模型上的應用。linux

  在個人看來,進程-線程模型能夠分爲非 Reactor 模式和 Reactor 模式兩種(固然還有 Proactor 模式,這種本文先不講,由於使用的比較少,並且我也還沒搞懂這種模式)。非 Reactor 模式和 Reactor 模式的兩種進程-線程模型下具體又分不少種,後面會一一列舉。非 Reactor 模式的進程-線程模型是傳統的模型,如今已經不多見,放在這裏主要是讓讀者作個瞭解同時與 Reactor 模式的進程-線程模式作個對比。nginx

非 Reactor 模式的進程-線程模型編程

  傳統模型不使用 IO 多路使用,因此問題比較多。初學者建議看下這部分,若是你以爲被迷糊了或者不感興趣能夠跳過該部分,直接看 Reactor 模式的部分便可。但該部分的第 一、2 點須要瞭解下。設計模式

  一、單進程單線程:服務器

  描述:這種模型全部的邏輯都在在一個進程中,包括創建鏈接->Read 鏈接上的數據->業務處理->Write 回一些數據而後一直循環下去。該模型一次只能處理一個鏈接,這個在真正的應用中是沒有的。初學者在模仿《Unix網絡編程卷 I》例子編寫網絡程序時應該會使用這種模型,固然例子中通常會在 Write 後面多個 Close 鏈接的動做,以避免在處理下一個鏈接的時候形成前一個句柄泄露。網絡

  優勢:代碼簡單,無需去了解進程、線程的概念,適合學習網絡編程的初學者。在不瞭解進程-線程模型狀況下的默認模型。多線程

  缺點:沒有任何實用價值。架構

  二、單進程多線程

  描述:進程只作創建鏈接的動做,每接收一個鏈接就建立一個線程,在此鏈接上的讀->業務處理->寫->關閉鏈接都在線程中去作,能夠採用線程池的方式減小線程的建立和銷燬。這種線程模型有必定的應用場景,Tomcat 三種線程模型之 BIO 用的就是這種進程-線程模型。初學者在學習完單進程單線程模型後對線程有所瞭解便可開始學習該種模型,能夠實現一個簡單的聊天室程序。

  優勢:能夠同時與多個 Client 創建鏈接,接收鏈接和處理鏈接業務分開。

  缺點:每一個鏈接佔用一個線程,當鏈接上沒有數據的時候形成線程資源浪費,能夠創建的鏈接數比較有限。

  三、多進程單線程

  描述:

  (1) 主進程啓動時建立監聽套接字並監聽,而後 fork 出 N 個子進程。

  (2) 因爲父子進程的繼承性,子進程同時也在端口監聽,而後在父進程中關閉監聽。

  (3) 父進程負責子進程的建立、銷燬、資源回收等,子進程負責鏈接的創建->Read->業務處理->Write 等。

  因爲全部進程都在同一個端口監聽,該模型會出現一個比較知名的現象---驚羣現象:當有一個鏈接來臨時,全部子進程都會被喚醒,可是最後能與 Client 創建鏈接的只有一個,形成資源浪費(系通調度也是消耗 CPU 的)。不過 linux 2.6 版本之後已經在內核消除了驚羣,當有鏈接來臨只會喚醒一個等待在 accept() 上的進程。即便內核沒修復,在應用層也能夠用鎖的方式防止驚羣。

  缺點:這種模型是單進程單線程的進化版本,然而並無什麼卵用。且增長了開發的難度。因此不列出它的優勢,介紹這種模型主要是引出驚羣的概念,在後面的 Reactor 模型中的多進程狀況下也會出現相似的狀況。

Reactor 模式的進程-線程模型

  該模式通常是 Reactor 模型 + IO 多路複用,下面的任何一種模型都具備必定的實用場景。

  一、單進程單線程:

  描述:只有一個進程,監聽套接字和鏈接套接字上的事件都由 Select 來處理,

  (1) 若是有創建鏈接的請求過來,Acceptor 負責接受並與之創建鏈接,同時將鏈接套接字加入 Select 進行監聽;

  (2) 若是某個鏈接上有讀事件則進行 Read->業務處理->Write 等操做;

  (3) 如此循環反覆。

  優勢:編程簡單,對於業務處理不復雜的後臺,基本能知足服務器端網絡編程。老東家的服務器端程序全是這種模式,主要緣由有以下緣由

  (1) 若是一臺機器性能不行,那就向集羣中新增一臺。

  (2) 業務處理並不複雜。

  (3) 擴展成多進程的話,若是不是多核意義不大。

  (4) 若是採用單進程多線程,C++ 處理線程不像 Java 簡單,還要考慮併發的問題,收益比不大。

  缺點:會有阻塞,在進行業務處理的時候不能進行其餘操做:如創建鏈接,讀取其餘套接字上的數據等。

  二、單進程多線程:

  描述:與單進程單線程相似,不一樣的是該模型將業務處理放在線程中,進程就不會阻塞在業務處理上。

  優勢:比較完美的進程-線程模型,在 Java 實現中複雜度也不高。不少網絡庫都是基於此,好比 Netty 。

  缺點:待補充。

  三、多進程單線程:

  描述:與非 Reactor 模式中的多進程單線程類似,只是本模式在子進程中使用了 IO 多路複用,實用性如下就上來了。大名鼎鼎的 nginx 就採用這種進程-線程模型

  優勢:編程相對簡單,充分利用多核。能知足高併發,否則 nginx 也不可能採用這種模式。

  缺點:子進程仍是會阻塞在業務處理上。

  四、多進程多線程

  描述:這裏再也不畫出圖形,就是在在子進程上將業務處理交給多線程處理,參考單進程多線程裏的線程池那裏。

  優勢:充分利用多核同時子進程不會阻塞在業務處理上

  缺點:編程複雜。

  五、主從進程 +多線程:

  描述:前面幾種 Reactor 模式的進程-線程模型中,鏈接的創建和鏈接的讀寫都是在同一進程中。本模型中將鏈接的創建和鏈接讀寫放在不一樣的進程中。

  (1) 主進程在監聽套接字上 Select 阻塞,一旦有請求過來則與之創建鏈接,並將鏈接套接字傳遞給從進程。

  (2) 從進程在鏈接套接字上 Select 阻塞,一旦鏈接上有數據過來則進行 Read,並將業務處理經過線程來處理。若是有必要還會向鏈接 Write 數據。

  優勢:鏈接的創建和鏈接的讀寫分開在不一樣進程中,處理效率會更高。該模型比單進程多線程模式還更優一點,且也能夠利用多核。

  缺點:編程複雜。

  以上就是常見的進程-線程模型,使用了 IO 多路複用的線程模型通常均可以稱爲 Reactor 模型,因此不用糾結 IO 多路複用與 Reactor 模式之間的關係。由 C++ 轉 Java 後,雖然再也不從事網咯編程。可是在看完《Netty 權威指南》後又想結合以前的工做經驗講講一個網絡庫設計須要考慮的一些要素,同時作一些 Netty 的分享。在後續的文章中會出,敬請期待!記得關注下哦~

 

相關文章
相關標籤/搜索