在上一章中,我们看了处理IO事件的过程,今天,我们瞅瞅处理异步任务队列。
3、处理异步任务队列
在执行完processSelectedKeys方法后,netty会继续执行runAllTasks方法,在观摩这个方法之前,我们了解下netty的task。
在初始化NioEventLoop的时候,会实例化两种task:普通task和scheduledTask,我们分别看看他们:
(1)普通的task
当我们调用NioEventLoop的execute方法,看看是NioEventLoop都做了什么操作:
无论是不是外部线程调用execute方法,都会执行addTask方法,进入该方法:
进入offerTask方法:
这个taskQueue是什么?还记的mpsc队列么?这个是一个多生产者单消费者安全线程,因此,execute方法会向mpsc队列中塞入task,该操作是线程安全的。
(2)scheduledTask
在调用NioEventLoop的schedule方法时候,瞅瞅NioEventLoop做了啥?
进入schedule方法:
如果是reactor线程,就执行scheduledTaskQueue().add(task);否则就通过execute执行scheduledTaskQueue().add(task);这两个有什么区别呢?
首先,reactor线程内是本线程执行scheduledTaskQueue().add(task);,所以是线程安全。当外部线程调用schedule方法时,就有可能会出现线程安全问题,那么这里通过execute方法执行scheduledTaskQueue().add(task);,说明scheduledTaskQueue应该不是线程安全的队列。为了验证我们的猜想,我们进入scheduledTaskQueue方法瞧瞧:
很明显,这个scheduledTaskQueue是一个非现场安全的队列,因此证明了我们的观点。先让scheduledTaskQueue().add(task);是线程安全,就得在把该操作放入线程安全的mpsc队列中。
OK,知道了NioEventLoop的两个任务队列,我们进入主题,瞧瞧runAllTasks方法。进入该方法:
/**
* Poll all tasks from the task queue and run them via {@link Runnable#run()} method. This method stops running
* the tasks in the task queue and returns if it ran longer than {@code timeoutNanos}.
*/
protected boolean runAllTasks(long timeoutNanos) {
fetchFromScheduledTaskQueue();
Runnable task = pollTask();
if (task == null) {
afterRunningAllTasks();
return false;
}
final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
long runTasks = 0;
long lastExecutionTime;
for (;;) {
safeExecute(task);
runTasks ++;
// Check timeout every 64 tasks because nanoTime() is relatively expensive.
// XXX: Hard-coded value - will make it configurable if it is really a problem.
if ((runTasks & 0x3F) == 0) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
if (lastExecutionTime >= deadline) {
break;
}
}
task = pollTask();
if (task == null) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
break;
}
}
afterRunningAllTasks();
this.lastExecutionTime = lastExecutionTime;
return true;
}
先看一下聚合聚合方法fetchFromScheduledTaskQueue:
进入pollScheduledTask方法:
该方法是从scheduledTaskQueue获取第一个scheduledTask。再回到fetchFromScheduledTaskQueue:
循环从scheduledTaskQueue中获取scheduledTask塞入到taskQueue中,此处的处理还是非常小心的,如果塞入失败后,再将该task放回到scheduledTaskQueue。回到runAllTasks方法中,我们继续向下看。
进入pooTask方法:
继续进入pollTaskFrom方法:
从taskQueue中获取第一个task。回到runAllTasks方法上,继续向下看:
这一步就是本章核心代码了。首先调用safeExecute方法,进入该方法搂一眼:
仅仅是执行task的run方法。
执行完safeExecute方法后,就将runTasks自增1。然后每隔0x3F即64个任务就判断当前时间是否超过了本次reactor任务循环的截至时间,如果超过了就执行break,否则就继续取task执行。
好了,处理异步任务队列讲完了,总结一下:
首先,netty会将已经到期的定时任务放入到MPSC队列中,然后循环执行task的run方法。