文中所講基本都是以非阻塞IO、異步IO為基礎。對于阻塞式IO,下面的程式設計模型幾乎都不适用
Reactor三種線程模型
單線程模型
單個線程以非阻塞IO或事件IO處理所有IO事件,包括連接配接、讀、寫、異常、關閉等等。單線程Reactor模型基于同步事件分離器來分發事件,這個同步事件分離器,可以看做是一個單線程的while循環。下圖描述了單線程模型的處理過程,看起來與網上大部分資料的圖檔不同,但本質是相同的。

注意上面的Selector之是以會有OP_ACEEPT事件,是因為在單線程模型中,Selector輪詢的是監聽套接字與已連接配接用戶端套接字的所有IO事件。
單線程處理所有IO事件的弊端很明顯。沒能利用計算機CPU多核的特性,一個線程某個時刻隻能處理單個IO事件,此時如果有其他描述符有IO事件就緒(如來了一個新的連接配接),這些IO事件将暫時得不到處理。
C++架構libevent中,基于event_base_loop做消息輪詢,使用event_base_dispatch來分發IO消息,本質上是對上述模型的封裝。如果不使用evthread_use_pthreads,則其預設就是單線程模型處理請求。
多線程模型
一個線程/程序接收連接配接、一組線程/程序處理IO讀寫事件。也就是将accept的線程與處理讀、寫等IO事件的線程分離,并且使用m多個線程、使用非阻塞IO或者事件IO來處理n個套接字的IO事件,這裡的n一般遠大于m,m一般取CPU邏輯核心數的1-3倍,而套接字數n則取決于請求數和程序可以打開的最大描述符個數。下圖是多線程模型
可以看到,這裡把用戶端的已連接配接套接字,轉交給某個IO線程之後,由此線程輪詢處理其之後的所有IO事件,這實際參考了netty4的線程模型設計。實際reactor的多線程模型,并不需要将已連接配接套接字綁定在某個線程上,也可以統一放在連接配接池中,由多個IOWork線程從池中取連接配接進行輪詢并處理,但這樣會複雜很多,而且容易出問題,比如說不同線程從同一個channel收到了write事件,這就類似驚群問題了;并且多線程并發操作同一個channel,後續很可能需要你講IO事件進行同步,與其如此,不如直接将channel綁定到一個線程,讓channel上觸發與處理IO事件邏輯上同步。netty3中channel(已連接配接套接字)入站事件由固定線程處理,出站事件由觸發的線程處理,netty4中修改了設計,将channel綁定到固定的eventloop(線程)。
另外一點,每個已連接配接套接字的IO事件由固定線程處理,不代表事件也一定由此線程觸發,恰恰相反,實際業務中,讀(入站)事件來自于用戶端寫資料觸發,而寫(出站)事件往往由别的線程觸發,例如在發起一個異步mysql操作完成之後,在異步回調線程中寫結果資料來觸發套接字的出站。
主從多線程模型
一組線程/程序接收連接配接、一組線程/程序處理IO讀寫事件。它與多線程模型的主要差別在于其使用一組線程或程序在一個共享的監聽套接字上accept連接配接。這麼做的原因是為了應付單個線程/程序不足以快速處理核心中監聽套接字的已連接配接套接字隊列(并發量極大)的情況。如下
主從多線程模型,有可能引起驚群效應。不過這個問題已經漸漸被規避,核心可以保證連接配接隻被唯一一個accept調用所擷取,其餘對此連接配接的accept調用将失敗。
Netty支援的線程模型
Netty支援單線程、多線程模型、主從多線程模型。但經本人多次測試、調試發現,ServerBootstrap預設不會使用主從多線程模型。雖然server支援設定EventLoopGroup(多個EventLoop)。但實際對于一個本地位址(IP+端口)進行accept,netty隻會将監聽套接字綁定到第一個EventLoop上,故隻會建立一個線程處理。
按本人的了解,Boss EventLoopGroup(Master EventLoopGroup,參數nThreads不為1)的作用主要用在對共享的監聽套接字或者多個本地位址監聽,對多個本地位址進行監聽一般表示一個JVM中有多個server,即有多個ServerBootStrap,這時,Boss EventLoopGroup可以通過共享給這多個ServerBootStrap起到作用(建立多個boss/master Thread)。
以下面的代碼為例MASTER_THREAD_CNT為4,但netty實際隻會使用第一個EventLoop,隻會給第一個EventLoop建立線程。
調試跟蹤源碼,可以明白netty的邏輯。
在ServerBootstrap繼承的initAndRegister方法中,調用MultithreadEventLoopGroup#register方法,此方法調用this.next擷取目前索引的下一個(索引位0,即是第一個)EventLoop。
然後register方法進一步調用register方法,在register中執行eventLoop.execute,這裡才會真正為監聽套接字建立第一個輪詢線程。
問題就在于在ServerBootstrap上調用bind方法,初始化監聽socket并綁定EventLoop時,是調用的next方法。是以netty隻會初始化第一個MasterEventLoop,如果想将MasterEventLoopGroup中的每個EventLoop都初始化,很顯然,需要重複綁定多個監聽套接字或者多次綁定一個可共享的套接字。