分享內容如下
- select啓動跟蹤
- 定時任務機制分析
- 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輪詢方法 各個步驟,爲後面深入分析打下基礎。