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


文中所講基本都是以非阻塞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都初始化,很顯然,需要重復綁定多個監聽套接字或者多次綁定一個可共享的套接字。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM