Netty 源码 NioEventLoop(一)初始化与启动

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

相关文章:

Netty 基于事件驱动模型,使用不同的事件来通知我们状态的改变或者操作状态的改变。它定义了在整个连接的生命周期里当有事件发生的时候处理的核心抽象。

  • Channel:Netty 网络操作抽象类,EventLoop 主要是为 Channel 处理 I/O 操作,两者配合参与 I/O 操作。

  • EventLoopGroup:一个 EventLoop 的分组,它可以获取到一个或者多个 EventLoop 对象,因此它提供了迭代出 EventLoop 对象的方法。

1. EventLoop 类图

其中 Executor、ExecutorService、AbstractExecutorService、ScheduledExecutorService 属于 JDK 定义的规范,Netty 实现了自己的自定义线程池。

  • EventExecutorGroup 提供了 next() 方法,除此之外是线程池的生命周期方法,如 shutdownGracefully。
  • EventLoopGroup 提供了 register 方法,将 channel 绑定到线程上。
  • EventExecutor 继承自 EventExecutorGroup,提供了 inEventLoop 方法。
  • EventLoop 继承自 EventLoopGroup,提供了 register 注册和 parent 方法。
  • MultithreadEventExecutorGroup 基于多线程的 EventExecutor (事件执⾏行器)的分组抽象类
  • ThreadPerTaskExecutor 实现 Executor 接⼝口,每个任务一个线程的执行器实现类

2. NioEventLoopGroup 初始化

(1) NioEventLoopGroup

NioEventLoopGroup 构造方法中最重要的一件事是创建子线程 NioEventLoop,创建完成后子线程并未启动,该线程在 channel 注册时启动。

public NioEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory,
final SelectorProvider selectorProvider, final SelectStrategyFactory selectStrategyFactory,
final RejectedExecutionHandler rejectedExecutionHandler) {
super(nThreads, executor, chooserFactory, selectorProvider, selectStrategyFactory, rejectedExecutionHandler);
}
  • nThreads, threadFactory 前两个参数用于创建线程池,如果 nThreads=0 则默认为 CPU 核数的 2 倍
  • chooserFactory 用于循环获取 NioEventLoopGroup 中的下一个 NioEventLoop 的算法。2 的幂次方使用位运算
  • selectorProvider, selectStrategyFactory 后二个参数用于创建 selector
  • rejectedExecutionHandler 异常处理的 Handler

(2) MultithreadEventExecutorGroup

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
// 1. executor 用于创建一个子线程
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
} // 2. 创建所有的子线程 children
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
children[i] = newChild(executor, args);
success = true;
} catch (Exception e) {
throw new IllegalStateException("failed to create a child event loop", e);
} finally {
if (!success) {
// 有异常则需要销毁资源
}
}
} // 3. chooser 用于循环获取 children 中的下一个元素的算法。2 的幂次方使用位运算
chooser = chooserFactory.newChooser(children); // 4. children 初始化完成则设置 setSuccess 为 true
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
if (terminatedChildren.incrementAndGet() == children.length) {
terminationFuture.setSuccess(null);
}
}
};
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
} // 5. 返回一个只读的 children 暴露给开发者
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}

MultithreadEventExecutorGroup 创建了所有的子线程,其中最重要的方法是 newChild(executor, args)

(3) newChild

// NioEventLoopGroup 创建子线程 NioEventLoop
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
return new NioEventLoop(this, executor, (SelectorProvider) args[0],
((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
}

(4) NioEventLoop

NioEventLoop 构造时就创建了一个 selector 对象。下面看一个 NioEventLoop 的创建过程。

// 创建了一个 selector 对象
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
provider = selectorProvider;
final SelectorTuple selectorTuple = openSelector();
selector = selectorTuple.selector;
unwrappedSelector = selectorTuple.unwrappedSelector;
selectStrategy = strategy;
} // 负责 channel 的注册
protected SingleThreadEventLoop(EventLoopGroup parent, Executor executor,
boolean addTaskWakesUp, int maxPendingTasks, RejectedExecutionHandler rejectedExecutionHandler) {
super(parent, executor, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler);
tailTasks = newTaskQueue(maxPendingTasks);
} // 负责执行任务
protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
boolean addTaskWakesUp, int maxPendingTasks, RejectedExecutionHandler rejectedHandler) {
super(parent);
this.addTaskWakesUp = addTaskWakesUp;
this.maxPendingTasks = Math.max(16, maxPendingTasks);
this.executor = ObjectUtil.checkNotNull(executor, "executor");
taskQueue = newTaskQueue(this.maxPendingTasks);
rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
}

通过以上步骤就创建了 NioEventLoop 对象,但这个线程并未启动。很显然在 channel 注册到 NioEventLoop 时会启动该线程。

3. NioEventLoop 启动过程

在 Channel 注册到 eventLoop 上时会执行 execute() 方法,启动线程。

// NioServerSocketChannel -> AbstractChannel.AbstractUnsafe
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
// 同一个 channel 的注册、读、写等都在 eventLoop 完成,避免多线程的锁竞争
if (eventLoop.inEventLoop()) {
// 将 channel 注册到 eventLoop 上
register0(promise);
} else {
// 若 eventLoop 线程没有启动,启动该线程
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
// 省略...
}
}
}

(1) execute

在 eventLoop 执行 execute 方法时,如果线程还未启动则需要先启动线程。

// SingleThreadEventExecutor
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
} // 是否在 EventLoop 线程中
boolean inEventLoop = inEventLoop();
if (inEventLoop) {
addTask(task);
} else {
startThread();
// 启动线程
addTask(task);
if (isShutdown() && removeTask(task)) {
reject();
}
} // 唤醒线程
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}

!addTaskWakesUp 表示“添加任务时,是否唤醒线程”?!但是,怎么使⽤用取反了。这样反倒变成了,“添加任务时,是否【不】唤醒线程”。具体的原因是为什么呢?

真正的意思是,“添加任务后,任务是否会自动导致线程唤醒”。为什么呢?

  • 对于 Nio 使用的 NioEventLoop ,它的线程执行任务是基于 Selector 监听感兴趣的事件,所以当任务添加到 taskQueue 队列中时,线程是无感知的,所以需要调用 #wakeup(boolean inEventLoop) 方法,进行主动的唤醒。

  • 对于 Oio 使用的 ThreadPerChannelEventLoop,它的线程执行是基于 taskQueue 队列列监听(阻塞拉取)事件和任务,所以当任务添加到 taskQueue 队列中时,线程是可感知的,相当于说,进行被动的唤醒。

(2) startThread 启动线程

private void startThread() {
if (state == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
try {
doStartThread();
} catch (Throwable cause) {
STATE_UPDATER.set(this, ST_NOT_STARTED);
PlatformDependent.throwException(cause);
}
}
}
} // 真正启动线程,执行的是 NioEventLoop 中的 run 任务
private void doStartThread() {
assert thread == null;
executor.execute(new Runnable() {
@Override
public void run() {
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
} boolean success = false;
updateLastExecutionTime();
try {
// run 方法由子类 NioEventLoop 实现,是一个死循环
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
// 如果执行到这里,则说明需要关闭该线程
}
}
});
}

executor 默认是在 MultithreadEventExecutorGroup 的构造方法完成初始化的,代码如下:

executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());

public final class ThreadPerTaskExecutor implements Executor {
private final ThreadFactory threadFactory; public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
if (threadFactory == null) {
throw new NullPointerException("threadFactory");
}
this.threadFactory = threadFactory;
} @Override
public void execute(Runnable command) {
threadFactory.newThread(command).start();
}
}

ThreadPerTaskExecutor 通过线程工厂 threadFactory 创建一个线程并启动,至此 NioEventLoop 开始工作了。在这个简单的类中使用的代理模式和命令模式两种设计模式。

(3) 线程状态变化

SingleThreadEventExecutor 维护了线程的状态字段 state。


每天用心记录一点点。内容也许不重要,但习惯很重要!

Netty 源码 NioEventLoop(一)初始化的更多相关文章

  1. Netty 源码 NioEventLoop(三)执行流程

    Netty 源码 NioEventLoop(三)执行流程 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 上文提到在启动 N ...

  2. Netty 源码(二)NioEventLoop 之 Channel 注册

    Netty 源码(二)NioEventLoop 之 Channel 注册 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 一 ...

  3. Netty源码分析第2章(NioEventLoop)---->第3节: 初始化线程选择器

    Netty源码分析第二章:NioEventLoop   第三节:初始化线程选择器 回到上一小节的MultithreadEventExecutorGroup类的构造方法: protected Multi ...

  4. Netty源码分析第1章(Netty启动流程)---->第3节: 服务端channel初始化

    Netty源码分析第一章:Netty启动流程   第三节:服务端channel初始化 回顾上一小节的initAndRegister()方法: final ChannelFuture initAndRe ...

  5. Netty源码分析第2章(NioEventLoop)---->第1节: NioEventLoopGroup之创建线程执行器

    Netty源码分析第二章: NioEventLoop 概述: 通过上一章的学习, 我们了解了Server启动的大致流程, 有很多组件与模块并没有细讲, 从这个章开始, 我们开始详细剖析netty的各个 ...

  6. Netty源码分析第2章(NioEventLoop)---->第2节: NioEventLoopGroup之NioEventLoop的创建

    Netty源码分析第二章: NioEventLoop   第二节: NioEventLoopGroup之NioEventLoop的创建 回到上一小节的MultithreadEventExecutorG ...

  7. Netty源码分析第2章(NioEventLoop)---->第4节: NioEventLoop线程的启动

    Netty源码分析第二章: NioEventLoop   第四节: NioEventLoop线程的启动 之前的小节我们学习了NioEventLoop的创建以及线程分配器的初始化, 那么NioEvent ...

  8. Netty源码分析第2章(NioEventLoop)---->第5节: 优化selector

    Netty源码分析第二章: NioEventLoop   第五节: 优化selector 在剖析selector轮询之前, 我们先讲解一下selector的创建过程 回顾之前的小节, 在创建NioEv ...

  9. Netty源码分析第2章(NioEventLoop)---->第6节: 执行select操作

    Netty源码分析第二章: NioEventLoop   第六节: 执行select操作 分析完了selector的创建和优化的过程, 这一小节分析select相关操作 跟到跟到select操作的入口 ...

随机推荐

  1. linux操作系统3 vi编辑器

    知识内容: 1.Linux基础命令复习 2.vi模式和基本操作 3.vi常用快捷键 一.Linux基础命令复习 目录(文件夹)操作 cd 进入目录 (tab自动补全) pwd 查看当前目录 mkdir ...

  2. 关于THINKPHP5模型关联的初步理解

    初步理解的意思是,使用最常用的关联模型,然后可以正常运行 还是打个比方 文章表  和文章分类表 一个文章分类可以有多个文章  所以  文章分类模型和文章建立 hasMany的关联 而文章和文章分类表则 ...

  3. angular 中怎么获取路径上的参数 参考:https://docs.angularjs.org/api/ng/service/$location

    参考: https://docs.angularjs.org/api/ng/service/$location

  4. jap 事务总结

    参考: JPA事务总结 2010年4月13日 - 从表11-2中可以看出,对于不同的EntityManager类型与所运行的环境,所支持的事务类型是不一样的. 其中两种情况下最为简单,一种是容器托管的 ...

  5. c++builder XE6 线程 tthread

    thread TThread class TSleepFunc : public TCppInterfacedObject<TProc> { public: TSleepFunc(TFor ...

  6. VB6 MsgBox 函数

    在对话框中显示消息,等待用户单击按钮,并返回一个值指示用户单击的按钮. MsgBox(prompt[, buttons][, title][, helpfile, context]) 参数 promp ...

  7. <、>&、<& 重定向符

    <.>&.<& 重定向符 这三个命令也是管道命令,但它们一般不常用,你只需要知道一下就ok了,当然如果想仔细研究的话,可以自己查一下资料.(本人已查过,网上也查不到相 ...

  8. 子元素margin-top后,跟父元素一起下沉

    在一个<div>元素中嵌套一个子div,同时设置子div的margin-top,结果,父元素和子元素一起下沉,留出来个空白区域. 原因就是:        一个盒子如果没有上补白(padd ...

  9. 使用 C++11 编写类似 QT 的信号槽——上篇

    了解 QT 的应该知道,QT 有一个信号槽 Singla-Slot 这样的东西.信号槽是 QT 的核心机制,用来替代函数指针,将不相关的对象绑定在一起,实现对象间的通信. 考虑为 Simple2D 添 ...

  10. 在eclipse中修改生成的注释类、方法的作者、时间

    1.打开eclipse:windos-->preferences-->Java-->Code Style -->Code Templates-->Types-->点 ...