1. 程式人生 > >Netty專欄 (五)——— EventLoop和執行緒模型

Netty專欄 (五)——— EventLoop和執行緒模型

Netty-In-Action

@author 魯偉林
記錄《Netty 實戰》中各章節學習過程,寫下一些自己的思考和總結,幫助使用Netty框架的開發技術人員們,能夠有所得,避免踩坑。
本部落格目錄結構將嚴格按照書本《Netty 實戰》,省略與Netty無關的內容,可能出現跳小章節。
本部落格中涉及的完整程式碼:
GitHub地址: https://github.com/thinkingfioa/netty-learning/tree/master/netty-in-action。
本人部落格地址: https://blog.csdn.net/thinking_fioa

第7章 EventLoop和執行緒模型

在本章開始前,推薦一本書籍《Java併發程式設計實戰》,此書應該是所有Java開發人員必讀書籍。

7.1 執行緒模型概述

基本的執行緒池化模式:

  1. 從執行緒池中的空閒執行緒列表中選擇一個Thread,指派去執行一個已提交的任務
  2. 任務完成後,該Thread重新回到執行緒池,使其可被重用

執行緒池通過快取和重用Thread,減少了執行緒建立和銷燬的過程,極大的提高了效能。但是它並不能消除上下文切換所帶來的開銷,隨著專案中執行緒資料量的增多,這種切換所帶來的開銷非常嚴重。

7.2 EventLoop介面

EventLoop通俗的理解就是:事件迴圈。直觀的程式碼邏輯:

while (!terminated) {
    List<Runnable> readyEvents = blockUntilEventsReady();
    for (Runnable ev: readyEvents) {
        ev.run();
    }
}

Netty提供的EventLoop結合了JDK的併發程式設計和Channel的事件,類層次如下 

約定俗成的關係(非常重要):

  1. 一個EventLoopGroup包含一個或多個EventLoop
  2. 一個EventLoop在其生命週期內只能和一個Thread繫結
  3. 所有I/O操作和事件都分配給EventLoop繫結的Thread處理
  4. 一個Channel在其生命週期內,只能註冊於一個EventLoop
  5. 一個EventLoop可能被分配處理多個Channel。也就是EventLoop與Channel是1:n的關係
  6. 一個Channel上的所有ChannelHandler的事件由繫結的EventLoop中的I/O執行緒處理
  7. 不要阻塞Channel的I/O執行緒,可能會影響該EventLoop中其他Channel事件處理
  8. EventLoop介面中只定義一個方法: parent() ----- 用於返回當前EventLoop實現的例項所屬的EventLoopGroup的引用

7.2.2 Netty3與Netty4中I/O操作區別

  1. Netty3中不保證多個執行緒不會在同一時刻訪問出站事件 ----- 不同的執行緒掉用Channel.write()方法,同一個Channel可能同時觸發出站事件。
  2. Netty4中所有的出站事件肯定是交由EventLoop繫結的執行緒非同步處理,所以不會存在Netty3多個執行緒訪問一個Channel的問題
  3. Netty3中exceptionCaught事件是一個入站事件,可能在Channel.write()出站事件發生時,發生異常,產生一個exceptonCaught入站事件,則會將異常交由I/O執行緒處理,存在上下文切換
  4. Metty4則保證Channel上所產生的所有I/O事件,都交由某個指定的EventLoop來處理

7.3 任務排程

排程一個任務以便稍後(延遲)執行或者週期性地執行

7.3.1 JDK的任務排程API

JDK提供Timer定時器和ScheduledExecutorService來實現排程功能。推薦使用ScheduledExecutorService。

ScheduledExecutorService與Timer定時器比較:

  1. Timer執行週期任務嚴格依賴於系統時間。比如,每5秒執行一次定時任務,當前時間是:2018-08-22 09:00:00,如果將機器時間調成昨天時間2018-08-21。那麼Timer定時任務將失效。而ScheduledExecutorService依然有效,其基於時間的延遲,與系統時間改變無關
  2. Timer是單執行緒,當執行多個任務時,任務1丟擲異常並且為正常處理,Timer執行緒將退出。所有的任務都不再被排程。而ScheduledExecutorService則保證task1出現異常時,不影響task2的執行
  3. Timer是單執行緒,如果task1執行非常耗時,則會影響task2執行。而ScheduledExecutorService則可以是多執行緒處理

7.3.2 使用EventLoop排程任務

Netty提供的EventLoop能夠幫助使用者實現週期性任務排程任務。從圖7-2中可以發現,EventLoop擴充套件了ScheduledExecutorService。

7.4 實現細節

7.4.1 執行緒管理

Netty執行緒模型的卓越效能取決於對於當前執行的Thread的身份的確定,通過呼叫EventExecutor的inEventLoop(Thread)方法實現。EventLoop會做以下判斷,以提交處理效能,減少執行緒切換代價

  1. 如果當前呼叫執行緒正是支援EventLoop的執行緒,那麼提交的程式碼塊將會被(直接)執行
  2. 否則,EventLoop將它放入到內部佇列中,以便稍後執行
  3. 注意:每個EventLoop都有自己的任務佇列,獨立於其他的EventLoop

下圖是Netty執行緒模型的關鍵組成部分[通過閱讀原始碼4.1.22發現,實現邏輯並非下圖,待考究

7.4.2 EventLoop執行緒的分配

根據不同的傳輸實現,EventLoop的建立和分配方式也不同

7.4.2.1 非同步傳輸

非同步傳輸通過儘可能少量的Thread來支援大量的Channel,而不是每個Channel分配一個Thread。少量的EventLoop可能會被多個Channel共享。

下圖3個固定大小的EventLoop(每個EventLoop都有一個Thread支撐),支援多個Channel的所有事件和任務 

  1. EventLoopGroup負責為每個新建立的Channel分配一個EventLoop。當前實現採用順序迴圈(round-robin)方式來進行分配
  2. 一旦一個Channel被分配給一個EventLoop,那麼它的整個生命週期中都使用這個EventLoop
  3. 提醒:由於EventLoop與Channel的對映關係是1:n,所以當使用ThreadLocal時,請開發人員知曉其中利弊。

7.4.2.2 阻塞傳輸

用於像OIO這樣的其他傳輸設計,每個Channel都將被分配給一個EventLoop(以及它的Thread),EventLoop與Channel的對映關係是1:1

附錄