1. 程式人生 > >Netty基礎:七、EventLoop的執行緒模型

Netty基礎:七、EventLoop的執行緒模型

1.執行緒池概述

執行緒模型確定了程式碼的執行方式。Java 5 隨後引入了ExecutorAPI,其執行緒池通過快取和重用Thread 極大地提高了效能。

  • 執行緒池基礎 >>執行緒池原理
    • 從池的空閒執行緒列表中選擇一個 Thread,並且指派它去執行一個已提交的任務(一個Runnable 的實現);
    • 當任務完成時,將該 Thread 返回給該列表,使其可被重用。

雖然池化和重用執行緒相對於簡單地為每個任務都建立和銷燬執行緒是一種進步,但是它並不能消除由上下文切換所帶來的開銷,其將隨著執行緒數量的增加很快變得明顯,並且在高負載下愈演愈烈。此外,僅僅由於應用程式的整體複雜性或者併發需求,在專案的生命週期內也可能會出現其他和執行緒相關的問題。

2.EventLoop介面

執行任務來處理在連線的生命週期內發生的事件是任何網路框架的基本功能。與之相應的程式設計上的構造通常被稱為事件迴圈——一個 Netty 使用了interface io.netty.channel.EventLoop來適配的術語。

事件迴圈的基本思想,其中每個任務都是一個Runnable的例項:

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

Netty的EventLoop是協同設計的一部分,它採用了兩個基本的 API:併發和網路程式設計。首先,io.netty.util.concurrent包構建在 JDK 的java.util.concurrent包上,用來提供執行緒執行器。其次,io.netty.channel包中的類,為了與Channel的事件進行互動,擴充套件了這些介面/類。

在這個模型中,一個EventLoop將由一個永遠都不會改變的Thread驅動,同時任務(Runnable 或者 Callable)可以直接提交給EventLoop實現,以立即執行或者排程執行。根據配置和可用核心的不同,可能會建立多個EventLoop

例項用以優化資源的使用,並且單個EventLoop可能會被指派用於服務多個Channel。Netty的EventLoop只定義了一個方法,parent()。這個方法,用於返回到當前EventLoop實現的例項所屬的EventLoopGroup的引用。

事件處理
由 I/O 操作觸發的事件將流經安裝了一個或者多個ChannelHandlerChannelPipeline。傳播這些事件的方法呼叫可以隨後被ChannelHandler所攔截,並且可以按需地處理事件。在Netty 4 中,所有的I/O操作和事件都由已經被分配給了EventLoop的那個Thread來處理。

3.任務排程

3.1.JDK的任務排程

JDK 提供了java.util.concurrent包,它定義了
interface ScheduledExecutorService介面,可以通過Executors的靜態方法生成它的例項。有興趣的請看 >>執行緒池相關

ScheduledExecutorService executor =
Executors.newScheduledThreadPool(10);
ScheduledFuture<?> future = executor.schedule(
    new Runnable() {
    @Override
        public void run() {
        System.out.println("60 seconds later");
        }
    //排程任務在從現在開始
的 60 秒之後執行
    }, 60, TimeUnit.SECONDS);
...
executor.shutdown();

雖然 ScheduledExecutorService API是直截了當的,但是在高負載下它將帶來效能上的負擔

3.2使用EventLoop排程任務

ScheduledExecutorService的實現具有侷限性,例如,事實上作為執行緒池管理的一部分,將會有額外的執行緒建立。如果有大量任務被緊湊地排程,那麼這將成為一個瓶頸。Netty 通過 Channel 的EventLoop實現任務排程解決了這一問題

Channel ch = ...
ScheduledFuture<?> future = ch.eventLoop().schedule(
    new Runnable() {
    @Override
    public void run() {
        System.out.println("60 seconds later");
    }
}, 60, TimeUnit.SECONDS);

經過 60 秒之後,Runnable 例項將由分配給 Channel 的EventLoop 執行。

如果當前呼叫執行緒正是支撐 EventLoop 的執行緒,那麼所提交的程式碼塊將會被(直接)執行。否則,EventLoop 將排程該任務以便稍後執行,並將它放入到內部佇列中。當 EventLoop下次處理它的事件時,它會執行佇列中的那些任務/事件。每個 EventLoop 都有它自已的任務佇列,獨立於任何其他的 EventLoop。
netty

4. EventloopGroup

EventLoopGroup 為 Netty 的 Reactor 執行緒池,它實際上就是 EventLoop 的容器,而 EventLoop 為 Netty 的核心抽象類,它的主要職責是處理所有註冊到本執行緒多路複用器 Selector 上的 Channel。我們知道 Eventloop 包含在 EventloopGroup 中,根據不同的傳輸實現,EventLoop 的建立和分配方式也不同。.

  • 一個 EventLoopGroup 包含一個或多個 EventLoop。
  • 一個 EventLoop 在它的生命週期內只能與一個Thread繫結。
  • 所有有 EnventLoop 處理的 I/O 事件都將在它專有的 Thread 上被處理。
  • 一個 Channel 在它的生命週期內只能註冊與一個 EventLoop。

當一個連線到達時,Netty 就會註冊一個 Channel,然後從 EventLoopGroup 中分配一個 EventLoop 繫結到這個Channel上,在該Channel的整個生命週期中都是有這個繫結的 EventLoop 來服務的。

4.1.非同步傳輸

非同步傳輸實現只使用了少量的 EventLoop(以及和它們相關聯的 Thread),而且在當前的執行緒模型中,它們可能會被多個 Channel 所共享。這使得可以通過儘可能少量的 Thread 來支撐大量的 Channel,而不是每個 Channel 分配一個 Thread。

netty
一個 EventLoopGroup,它具有 3 個固定大小的 EventLoop(每個 EventLoop都由一個 Thread 支撐)。在建立 EventLoopGroup 時就直接分配了 EventLoop(以及支撐它們的 Thread),以確保在需要時它們是可用的。

4.2.阻塞傳輸

netty
這種情況下保證是每個 Channel 的 I/O 事件都將只會被一個 Thread(用於支撐該 Channel 的 EventLoop 的那個 Thread)處理。