1. 程式人生 > >Netty 原始碼 NioEventLoop(三)執行過程

Netty 原始碼 NioEventLoop(三)執行過程

Netty 原始碼 NioEventLoop(三)執行過程

Netty 系列目錄(https://www.cnblogs.com/binarylei/p/10117436.html)

上文提到在啟動 NioEventLoop 執行緒時會執行 SingleThreadEventExecutor#doStartThread(),在這個方法中呼叫 SingleThreadEventExecutor.this.run(),NioEventLoop 重寫了 run() 方法。

@Override
protected void run() {
    for (;;) {
        try {
            // 1. select 策略選擇
            switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                // 1.1 非阻塞的 select 策略。實際上,預設情況下,不會返回 CONTINUE 的策略
                case SelectStrategy.CONTINUE:
                    continue;
                // 1.2 阻塞的 select 策略
                case SelectStrategy.SELECT:
                    select(wakenUp.getAndSet(false));
                    if (wakenUp.get()) {
                        selector.wakeup();
                    }
                // 1.3 不需要 select,目前已經有可以執行的任務了
                default:
            }

            // 2. 執行網路 IO 事件和任務排程
            cancelledKeys = 0;
            needsToSelectAgain = false;
            final int ioRatio = this.ioRatio;
            if (ioRatio == 100) {
                try {
                    // 2.1. 處理網路 IO 事件
                    processSelectedKeys();
                } finally {
                    // 2.2. 處理系統 Task 和自定義 Task
                    runAllTasks();
                }
            } else {
                // 根據 ioRatio 計算非 IO 最多執行的時間 
                final long ioStartTime = System.nanoTime();
                try {
                    processSelectedKeys();
                } finally {
                    final long ioTime = System.nanoTime() - ioStartTime;
                    runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
        // 3. 關閉執行緒
        try {
            if (isShuttingDown()) {
                closeAll();
                if (confirmShutdown()) {
                    return;
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
    }
}

NioEventLoop#run() 做了記下事情:

  1. 根據 selectStrategy 執行不同的策略
  2. 執行網路 IO 事件和任務排程
  3. 關閉執行緒

一、IO 輪詢策略

(1) hasTasks

首先,在 run 方法中,第一步是呼叫 hasTasks() 方法來判斷當前任務佇列中是否有任務

protected boolean hasTasks() {
    assert inEventLoop();
    return !taskQueue.isEmpty();
}

這個方法很簡單,僅僅是檢查了一下 taskQueue 是否為空。至於 taskQueue 是什麼呢,其實它就是存放一系列的需
要由此 EventLoop 所執行的任務列表。關於 taskQueue,我們這裡暫時不表,等到後面再來詳細分析它。

(2) DefaultSelectStrategy

// NioEventLoop#selectNowSupplier
private final IntSupplier selectNowSupplier = new IntSupplier() {
    @Override
    public int get() throws Exception {
        return selectNow();
    }
};

// 非阻塞的 select 策略。實際上,預設情況下,不會返回 CONTINUE 的策略
SelectStrategy.SELECT = -1;
// 阻塞的 select 策略
SelectStrategy.CONTINUE = -2;

// DefaultSelectStrategy 
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
    return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}

顯然當 taskQueue 為空時,執行的是 select(oldWakenUp) 方法。那麼 selectNow() 和 select(oldWakenUp) 之間有什麼區別呢? 來看一下,selectNow() 的原始碼如下

(3) selectNow

int selectNow() throws IOException {
    try {
        return selector.selectNow();
    } finally {
        // restore wakeup state if needed
        if (wakenUp.get()) {
            selector.wakeup();
        }
    }
}

呼叫 JDK 底層的 selector.selectNow()。selectNow() 方法會檢查當前是否有就緒的 IO 事件,如
果有,則返回就緒 IO 事件的個數;如果沒有,則返回 0。注意,selectNow() 是立即返回的,不會阻塞當前執行緒。當
selectNow() 呼叫後,finally 語句塊中會檢查 wakenUp 變數是否為 true,當為 true 時,呼叫 selector.wakeup() 喚
醒 select() 的阻塞呼叫。

(4) select(boolean oldWakenUp)

private void select(boolean oldWakenUp) throws IOException {
    Selector selector = this.selector;
    try {
        int selectedKeys = selector.select(timeoutMillis);
    } catch (CancelledKeyException e) {
    }
}

在這個 select 方法中,呼叫了 selector.select(timeoutMillis),而這個呼叫是會阻塞住當前執行緒的,timeoutMillis
是阻塞的超時時間。到來這裡,我們可以看到,當 hasTasks() 為真時,呼叫的的 selectNow() 方法是不會阻塞當前線
程的,而當 hasTasks() 為假時,呼叫的 select(oldWakenUp) 是會阻塞當前執行緒的。這其實也很好理解:
taskQueue 中沒有任務時,那麼 Netty 可以阻塞地等待 IO 就緒事件。而當 taskQueue 中有任務時,我們自然地希望
所提交的任務可以儘快地執行
,因此 Netty 會呼叫非阻塞的 selectNow() 方法,以保證 taskQueue 中的任務儘快可
以執行。


每天用心記錄一點點。內容也許不重要,但習慣很重要!