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. 外星人完事了,开始python的matplotlib玩转

    外星人完事了,开始python的matplotlib玩转 看书上的例子,在win下安装比较麻烦 今天用pip尝试了一下 pip install matplotlib 然后等待即可 安装完毕后 在pyt ...

  2. OpenCL 管道

    ▶ 按书上写的管道的代码,需要使用 OpenCL2.0 的平台和设备,目前编译不通过,暂时不知道是什么问题,先把代码堆上来,以后换了新的设备再说 ● 程序主要功能:用主机上的数组 srcHost 创建 ...

  3. yii 日期插件

    ——controller     public $defaultAction = "income";    public function actionIncome(){      ...

  4. sqlserver主从复制

    参考网站: http://www.178linux.com/9079 https://www.cnblogs.com/tatsuya/p/5025583.html windows系统环境进行主从复制操 ...

  5. HTML5 监听移动端浏览器返回键兼容版本

    // 往windosw对象中的历史记录注入URL的方法 function addUrl() { var state = { title: "title", url: "# ...

  6. eclipse中添加aptana插件(html.css.js自动提示)

    一.关于aptana aptana是一款很不错的插件,本人主要用于安装此类插件,在eclipse中用于编辑javascript代码,html代码,和css代码的,因为其有自动纠错功能,当然安装后的问题 ...

  7. centor os 安装nginx

    安装nginx和health check wget http://nginx.org/download/nginx-1.4.5.tar.gz git clone https://github.com/ ...

  8. Object-c 创建按钮

    @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //动态创建我们自己的按钮 //1.创建按钮(UIB ...

  9. NISP视频知识点总结

    身份认证访问控制安全审计本章实验 ===密码学=====古典密码 算法本身的保密性近代密码 机械密码\机电 密码打字密码机轮转机现代密码 基于密钥公钥密码 公钥==================对称 ...

  10. SQL Server - 最佳实践 - 参数嗅探问题 转。

    文章来自:https://yq.aliyun.com/articles/61767 先说我的问题,最近某个存储过程,暂定名字:sp_a 总是执行超时,sp_a带有一个参数,暂定名为 para1 var ...