Netty源碼閱讀之如何將TCP的讀寫操作和指定執行緒綁定

原文鏈接//xueliang.org/article/detail/20200712234015993

前言

在Netty的執行緒模型中,對於一個TCP連接的讀寫操作,都是由一個單執行緒完成的,對於剛入門Netty的新手,這完全顛覆我們熟知的多執行緒能夠加快處理速度,縮短處理時間的常規思路。
實際上,Netty採用了非同步通訊模式,一個IO 執行緒可以並發處理N 個客戶端連接和讀寫操作,這從根本上解決了傳統同步阻塞IO 一連接一執行緒模型,架構的性能、彈性伸縮能力和可靠性都得到了極大的提升。

源碼閱讀

Channel 註冊到 Worker 執行緒組上
register()

調用 NioEventLoopGroupnext() 從 Worker 執行緒組中獲取一個 eventLoop
next()

根據執行緒組個數不同,會調用 PowerOfTwoEventExecutorChooser 或者 GenericEventExecutorChoosernext() 方法,如果執行緒數是 2 的 N 次方,就選用 PowerOfTwoEventExecutorChooser 這個 EventLoop 選擇類,使用位運算提高效率
choose executor

調用選取的 eventLoopregister() 方法,可以看到,將 this 也就是當前 EventLoop 當做參數傳入 promise.channel().unsafe().register() 方法
eventLoop.register()

繼續進到 promise.channel().unsafe().register 方法,到這裡,終於將 eventLoop 賦值給了 Channel,即 ChanneleventLoop 建立了綁定關係。
channel - eventloop

但Channel還未與執行緒綁定,繼續往下看,當我們平時在Handler里調用 ctx (即 ChannelHandlerContext 類對象)的 write() 時,實際是獲取 ctxexecutor 執行寫操縱事件,若未給 ctx 指定 executor,則 ctx 會使用 對應的 channeleventLoop
ctx.eventLoop()

執行 eventLoopexecute() 方法
executor.execute()

進到 execute() 方法內,先通過調用 inEventLoop() 方法,判斷當前執行緒是否是 eventLoop 綁定的那個執行緒
eventLoop.inEventLoop()

如果不是,則可能 eventLoop 還沒有綁定執行緒,則調用 startThread 方法創建一個執行緒
eventLoop.execute()

最終調用 eventLoopdoStartThread() ,由 executor 指定創建執行緒的任務。
eventLoop.doStartThread()

到此,Channel – EventLoop – Thread 綁定在了一起,同時也能看出多個 Channel 可能綁定到 一個EventLoop上

總結

Netty將一個TCP連接和一個固定的執行緒綁定,不需要進行執行緒切換以及執行緒同步,即節省資源又提高吞吐效率,除此之外我們在閱讀源碼的過程中,從EventLoop的選取,根據不同的執行緒數,使用不同的輪詢器,可以看出Netty對於高性能的極致追求。

原文鏈接//xueliang.org/article/detail/20200712234015993

Tags: