Reactor三種線程模型與Netty線程模型

文中所講基本都是以非阻塞IO、異步IO爲基礎。對於阻塞式IO,下面的編程模型幾乎都不適用mysql

Reactor三種線程模型

單線程模型

單個線程以非阻塞IO或事件IO處理全部IO事件,包括鏈接、讀、寫、異常、關閉等等。單線程Reactor模型基於同步事件分離器來分發事件,這個同步事件分離器,能夠看作是一個單線程的while循環。下圖描述了單線程模型的處理過程,看起來與網上大部分資料的圖片不一樣,但本質是相同的。react

 

注意上面的Selector之因此會有OP_ACEEPT事件,是由於在單線程模型中,Selector輪詢的是監聽套接字與已鏈接客戶端套接字的全部IO事件。sql

單線程處理全部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鏈接。這麼作的緣由是爲了應付單個線程/進程不足以快速處理內核中監聽套接字的已鏈接套接字隊列(併發量極大)的狀況。以下socket

 

主從多線程模型,有可能引發驚羣效應。不過這個問題已經漸漸被規避,內核能夠保證鏈接只被惟一一個accept調用所獲取,其他對此鏈接的accept調用將失敗。oop

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都初始化,很顯然,須要重複綁定多個監聽套接字或者屢次綁定一個可共享的套接字。

相關文章
相關標籤/搜索