Netty原始碼閱讀之如何將TCP的讀寫操作和指定執行緒繫結
原文連結:http://xueliang.org/article/detail/20200712234015993
前言
在Netty的執行緒模型中,對於一個TCP連線的讀寫操作,都是由一個單執行緒完成的,對於剛入門Netty的新手,這完全顛覆我們熟知的多執行緒能夠加快處理速度,縮短處理時間的常規思路。
實際上,Netty採用了非同步通訊模式,一個IO 執行緒可以併發處理N 個客戶端連線和讀寫操作,這從根本上解決了傳統同步阻塞IO 一連線一執行緒模型,架構的效能、彈性伸縮能力和可靠性都得到了極大的提升。
原始碼閱讀
將 Channel
註冊到 Worker 執行緒組上
呼叫 NioEventLoopGroup
next()
從 Worker 執行緒組中獲取一個 eventLoop
根據執行緒組個數不同,會呼叫 PowerOfTwoEventExecutorChooser
或者 GenericEventExecutorChooser
的 next()
方法,如果執行緒數是 2 的 N 次方,就選用 PowerOfTwoEventExecutorChooser
這個 EventLoop
選擇類,使用位運算提高效率
呼叫選取的 eventLoop
的 register()
方法,可以看到,將 this
也就是當前 EventLoop
當做引數傳入 promise.channel().unsafe().register()
繼續進到 promise.channel().unsafe().register
方法,到這裡,終於將 eventLoop
賦值給了 Channel
,即 Channel
與 eventLoop
建立了繫結關係。
但Channel還未與執行緒繫結,繼續往下看,當我們平時在Handler裡呼叫 ctx
(即 ChannelHandlerContext
類物件)的 write()
時,實際是獲取 ctx
的 executor
執行寫操縱事件,若未給 ctx
指定 executor
,則 ctx
會使用 對應的 channel
的 eventLoop
執行 eventLoop
的 execute()
進到 execute()
方法內,先通過呼叫 inEventLoop()
方法,判斷當前執行緒是否是 eventLoop
繫結的那個執行緒
如果不是,則可能 eventLoop
還沒有繫結執行緒,則呼叫 startThread
方法建立一個執行緒
最終呼叫 eventLoop
的 doStartThread()
,由 executor
指定建立執行緒的任務。
到此,Channel - EventLoop - Thread 繫結在了一起,同時也能看出多個 Channel 可能繫結到 一個EventLoop上
總結
Netty將一個TCP連線和一個固定的執行緒繫結,不需要進行執行緒切換以及執行緒同步,即節省資源又提高吞吐效率,除此之外我們在閱讀原始碼的過程中,從EventLoop的選取,根據不同的執行緒數,使用不同的輪詢器,可以看出Netty對於高效能的極致追求。