netty源碼分析6-NioEventLoop啓動和run方法

分享內容如下

  1. select啓動跟蹤
  2. 定時任務機制分析
  3. NioEventLoopIO輪詢方法分析

select啓動跟蹤

NioEventLoop中有循環select實現,是什麼時候被調用的?

循環select由run方法實現,調用鏈如下圖

6

ServerBootstrap(AbstractBootstrap<B,C>).initAndRegister() line: 27

代碼如下:

channel.unsafe().register(regFuture);

AbstractNioMessageChannel$NioMessageUnsafe(AbstractChannel$AbstractUnsafe).register(ChannelPromise) line: 396

代碼如下:

public final void register(final ChannelPromise promise) {

if (eventLoop.inEventLoop()) {

register0(promise);

} else {

try {//這裏的eventLoop 是parentGroup中的

eventLoop.execute(new Runnable() {// 不是直接提交Task,裏面有NioEventLoop的啓動處理。

@Override

public void run() {

register0(promise);

}

});

} catch (Throwable t) {

//.....省略代碼.

}

}

}

NioEventLoop-execute

public void execute(Runnable task) {

if (task == null) {

throw new NullPointerException("task");

}

 

boolean inEventLoop = inEventLoop();

if (inEventLoop) {

addTask(task);

} else {

startThread();

addTask(task);

if (isShutdown() && removeTask(task)) {

reject();

}

}

 

if (!addTaskWakesUp) {

wakeup(inEventLoop);

}

}

處理邏輯:如果啓動添加任務,否則啓動reactor 輪詢IO事件和提交任務,然後添加任務。

啓動線程-doStartThread

private void doStartThread() {

assert thread == null;

//executor type is io.netty.util.concurrent.ThreadPerTaskExecutor

//參照NioEventLoopGroup篇中對ThreadPerTaskExecutor的分析,下面代碼 啓動一個線程執行run方法

executor.execute(new Runnable() {

@Override

public void run() {

//.......

// 執行循環select

SingleThreadEventExecutor.this.run();

/.........

}

}

}

 

兩個小問題分析

(1)inEventLoop() 調用鏈

NioEventLoop(SingleThreadEventExecutor).inEventLoop(Thread) line: 388

NioEventLoop(AbstractEventExecutor).inEventLoop() line: 62

得知執行的是 AbstractEventExecutor的實現

public boolean inEventLoop(Thread thread) {

// 變量此時 this.thread==null ?

return thread == this.thread;

}

(2)eventLoop是怎麼獲取的

@Override

Channel createChannel() {

// 這裏的group是 parentGroup();

EventLoop eventLoop = group().next();

return channelFactory().newChannel(eventLoop, childGroup);

}

inEventLoop() 分析

inEventLoop():判斷已經記錄的現場是否是當前線程。記錄線程只有有在啓動NioEventLoop時才執行,server啓動時還沒記錄線程,這個時候就會在提交的同時啓動線程。

inEventLoop()是一個很重要的判斷,在執行IO操作和channel事件都有用到,主要是判斷是否需要無鎖化處理。

無鎖化處理參照 《ChannelPipeline和ChannelHandler》分析

小結:eventLoop.execute()第一次執行時啓動了 循環執行select。

 

定時任務機制分析

SingleThreadEventExecutor:

private void startThread() {

synchronized (stateLock) {

if (state == ST_NOT_STARTED) {

state = ST_STARTED;

delayedTaskQueue.add(new ScheduledFutureTask<Void>(

this, delayedTaskQueue, Executors.<Void>callable(new PurgeTask(), null),

ScheduledFutureTask.deadlineNanos(SCHEDULE_PURGE_INTERVAL), -SCHEDULE_PURGE_INTERVAL));//line A

 

doStartThread();

}

}

}

方法中加粗的lineA 的作用:

delayedTaskQueue用來存儲定時任務,這裏是添加了一個定時任務。

分析一下 ScheduledFutureTask 的run方法

public void run() {

assert executor().inEventLoop();

try {

if (periodNanos == 0) {

if (setUncancellableInternal()) {

V result = task.call();//這裏執行的是傳進來的callable方法

setSuccessInternal(result);

}

} else {

// check if is done as it may was cancelled

if (!isCancelled()) {

task.call();

if (!executor().isShutdown()) {

long p = periodNanos;

if (p > 0) {

deadlineNanos += p;

} else {

deadlineNanos = nanoTime() - p;

}

if (!isCancelled()) {

delayedTaskQueue.add(this);

}

}

}

}

} catch (Throwable cause) {

setFailureInternal(cause);

}

}

處理邏輯:如果執行事件間隔值爲0,也就是執行1次就可以,不需要循環,則只執行。如果執行事件間隔值大於0且任務沒有被取消,說明該任務需要循環執行,執行任務後,將該任務再加入任務隊列尾部。

 

下面分析一下PurgeTask 幹了什麼

//淨化任務?

private final class PurgeTask implements Runnable {

@Override

public void run() {

Iterator<ScheduledFutureTask<?>> i = delayedTaskQueue.iterator();

while (i.hasNext()) {

ScheduledFutureTask<?> task = i.next();

if (task.isCancelled()) {

i.remove();

}

}

}

}

這些定時任務不斷被輪詢出來執行,其中這個PurgeTask 用來清理已經取消的任務。

 

什麼時候定時任務會被取消?如HeartBeatReqHandler

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)

throws Exception {

cause.printStackTrace();

if (heartBeat != null) {

heartBeat.cancel(true);

heartBeat = null;

}

ctx.fireExceptionCaught(cause);

}

當HeartBeatReqHandler 執行exceptionCaught, 心跳任務會被取消。

 

 

NioEventLoop(SingleThreadEventExecutor).fetchFromDelayedQueue() line: 208

這個方法在每次執行 IO任務前調用。

private void fetchFromDelayedQueue() {

long nanoTime = 0L;

for (;;) {

//每次取出任務 卻不刪除?

ScheduledFutureTask<?> delayedTask = delayedTaskQueue.peek();

if (delayedTask == null) {

break;

}

if (nanoTime == 0L) {

nanoTime = ScheduledFutureTask.nanoTime();

}

if (delayedTask.deadlineNanos() <= nanoTime) {//定時任務到達或超出截止時間

delayedTaskQueue.remove();//移除這個任務,不用擔心 下次的執行,ScheduledFutureTask run方法執行完,會將任務加入delayedTask隊尾。

taskQueue.add(delayedTask);//加入執行任務隊列

} else {

break;

}

}

}

 

fetchFromDelayedQueue() 主要用來撈取已經到時間執行的定時任務加入執行隊列。

經過分析 delayedTaskQueue只用於存放任務,不直接作用於任務執行。

 

除了啓動會添加一個定時任務,NioEventLoop體系中還提供了 定時任務的執行方法。

這個schedule方法是多個schedule方法的核心實現。

這個方法只是將task加入了隊列,執行的過程還是依賴上面幾個方法

代碼比較容易理解,如下:

private <V> ScheduledFuture<V> schedule(final ScheduledFutureTask<V> task) {

if (task == null) {

throw new NullPointerException("task");

}

 

if (inEventLoop()) {

delayedTaskQueue.add(task);

} else {

execute(new Runnable() {

@Override

public void run() {

delayedTaskQueue.add(task);

}

});

}

 

return task;

}

小結:netty 實現了一套定時任務添加和執行機制,依賴NioEventLoop輪詢執行任務實現。

 

NioEventLoop IO輪詢方法分析

//添加了任務 沒有真正執行

NioEventLoop(SingleThreadEventExecutor).execute(Runnable)

執行方法NioEventLoop-run()

protected void run() {

for (;;) {

oldWakenUp = wakenUp.getAndSet(false);

try {

if (hasTasks()) {

//非阻塞select

selectNow();

} else {

//阻塞select

select();

//wakenUp 後面詳細分析

if (wakenUp.get()) {

selector.wakeup();

}

}

 

cancelledKeys = 0;

 

final long ioStartTime = System.nanoTime();

needsToSelectAgain = false;

//執行select事件

if (selectedKeys != null) {//使用優化的SelectionKey

processSelectedKeysOptimized(selectedKeys.flip());

} else {//使用原生的SelectionKey

processSelectedKeysPlain(selector.selectedKeys());

}

//processSelectedKeysOptimized 和processSelectedKeysPlain 使用的 SelectionKey不同,其他邏輯處理一致

final long ioTime = System.nanoTime() - ioStartTime;

 

final int ioRatio = this.ioRatio;

//執行IO任務

//這裏計算的時間值用於限制 IO任務執行時間,保證IO事件和IO任務執行的比例,默認是50:50

runAllTasks(ioTime * (100 - ioRatio) / ioRatio);

 

if (isShuttingDown()) {

closeAll();

if (confirmShutdown()) {

break;

}

}

} catch (Throwable t) {

//。。。。。。

}

}

}

處理步驟:先輪詢事件,然後執行事件,然後執行任務。

目前只是 簡單的分析了NioEventLoop IO輪詢方法 各個步驟,後面文章會對 run方法中的select執行,IO事件,IO任務,NioEventLoop父子類進行詳細分析。

總結:本文對 NioEventLoop啓動和過程中用到的定時任務機制進行了詳細分析,也簡單的分析了NioEventLoop IO輪詢方法 各個步驟,爲後面深入分析打下基礎。

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章