Netty源码分析--Channel注册(中)(六)

接上一篇,我们继续看

不知道大家第一次看这段代码的时候有没有一脸懵逼,反正我是一脸懵,为什么这个if else 最终都是调用的register0方法,都是一样的。

其实这里就是为什么Netty是线程安全的根本原因。

我们先看下 eventLoop.inEventLoop() 方法

第一张图传入了 当前的 线程, 第二个图 判断了 当前这个NioEventLoop中的Thread 是不是和当前线程相等, 如果相等返回true, 相反就是false.

我们debug 看一下

发现NioEventLoop中的Thread 当前并没有赋值, 值是null,所以返回false.

那么代码也就进入到了

这里其实也容易漏看,其实这里不只是启动一个子线程来执行register0, 其实在这之前还做了好多时间。

我们进入eventLoop的execute()方法,惊喜不。。。

inEventLoop的值肯定是false,  然后执行addTask(task),把当前这个任务(register0)加入到队列中,看下这个队列

这个队列是一个LinkedBlockingQueue.

继续

这就是举世闻名的CAS无锁技术,当然不了解CAS的自行百度。这里我想说的是,大家可以学习一下Netty这种写法。

CAS方式原子性更新state字段的值,这里的state一定要使用volatile修饰,这个关键字不太了解的,也自行百度。

回到  startThread() 方法, 先检查一下Thread 是否已经启动, 如果没有启动,就把state原子性改成 启动状态 ,如果在启动过程中出现异常,则再次把state原子性改成 未启动状态。

继续进入 doStartThread() 方法

先是一个断言来保证thread一定是null, 然后启动一个子线程,并把当前这个子线程 赋值给了当前的 这个NioEventLoop 中的 thread 成员变量。 ok ,到现在为止,NioEventLoop 中的唯一线程确定。

从这里我们进入run() 方法

我们发现进入到了一个死循环, 然后里面有一个switch分支,我们来看下里面的策略计算方法。

在说这个之前我们再来一起看一个NIO中多路复用器的API

不会阻塞,不管什么通道就绪都立刻返回(译者注:此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。

同时这个方法会清除wakeup()方法的效果。

此方法执行阻塞的 selection operation 。 只有在选择了至少一个通道之后,才会返回此选择器的wakeup方法,当前线程被中断,或给定的超时期限到期,以先到者为准。

此方法不提供实时保证:它调用超时,就像调用Object.wait(long)方法一样。

如果另一个线程在调用select()select(long)方法时被阻止,则该调用将立即返回。 如果当前没有选择操作,则下一次调用这些方法之一将立即返回,除非在此期间调用selectNow()方法。 无论如何,该调用返回的值可能不为零。 的后续调用select()select(long)除非此方法在此期间再次调用的方法将阻塞如常。 在两次连续的选择操作之间多次调用此方法与调用它一样具有相同的效果

好了,了解了这些我们继续看,

先检查是否有待处理的task,如果有那么就非阻塞的检查一下是否有新的channel被注册,然后返回channel注册的数量,可能是0, 如果没有task,则返回 - 1

我们发现如果有task,那么这么switch就直接跳出了。如果返回 - 1 ,就执行 select(wakenUp.getAndSet(false))

我们先看下没有task的情况吧。先大概读一下这一大段注释

大概的意思是说:

在调用选择器唤醒方法,之前,先确定wakenUp的值,以减少唤醒负载,因为唤醒选择器是一个耗时的操作。  但是不能把warkup设置true太早,将会触发竞争。

1、选择器在wakenUp属性更新为false和选择操作之间被唤醒

2、选择器在选择操作和获取wakenUp属性之间

在第一种情况下,当wakenUp属性更新为true,接下来的选择操作就会立刻被唤醒, 直到在下一次循环中wakenUp属性更新为false,wakenUp.compareAndSet(false, true)  ,将会失败,同时引起下一次不必要的选择操作阻塞, 怎么这句话呢(自己的理解)。

我们看一下这个方法 select(wakenUp.getAndSet(false))

首先我们假如入参当前是false, 也就是 oldWakenUp = false

那么再假如当前是有task待处理的,那么也就是说  hasTasks() && wakenUp.compareAndSet(false, true)  == true , 那么将执行selectNow(), 也就是当前时间到上一次select操作的期间内是否有channel注册进来。

然后break,接下来

wakeUp 刚刚被CAS 成 true ,所以这里会执行wakeup操作,也就意味着下一次select操作将会被立即返回。

接下来就是去处理task 和 新接入的客户端或者读写操作了(一会再说这个)。

因为是死循环,我们继续回来,又到了

这次的wakeUp 变成了true, 并且把状态置为false, 那么也就是说  oldWakenUp = true

这里不管有没有任务,都会立即返回,因为我们之前执行了selector.wakeup(),这里我自己猜测可能是因为处理读写和任务用掉了很长时间,所以这里直接就检查当前是会有channel已经注册进来已经在等待了。

如果有的话,直接break.去执行。

当然如果之前没有 selector.wakeup() 过,那么将会执行 1s 的时间,看着1s 内是否有新的channel进来。

继续看,通过这两段我们发现如果循环超时了,那么将会break掉。

通过这两段我们发现,当循环512次之后,那么将会重建Selector

这里其实是因为JDK的BUG导致的,会把CPU飚到100%

整个重建的过程其实就是,创建新的selector,把老的上面的 SelectionKey 都注册到新的selector上,然后将老的selector关闭掉,具体的内容就不一起看了。

原文地址:https://www.cnblogs.com/huxipeng/p/11042918.html

时间: 2024-08-30 16:05:19

Netty源码分析--Channel注册(中)(六)的相关文章

Netty源码分析--Channel注册(上)(五)

其实在将这一节之前,我们来分析一个东西,方便下面的工作好开展. 打开启动类,最开始的时候创建了一个NioEventLoopGroup 事件循环组,我们来跟一下这个. 这里bossGroup, 我传入了一个线程, workerGroup 没有入参,默认0, 也就是说父级我用一个线程来处理客户端的接入, 多个线程来处理客户端的读写操作 通过一连串的构造方法进入到 executor 是 null , selectorProvider 用来创建一个多路复用器, 最后一个参数传入了 一个多路复用器的一个策

Netty源码分析-- 处理客户端接入请求(八)

这一节我们来一起看下,一个客户端接入进来是什么情况.首先我们根据之前的分析,先启动服务端,然后打一个断点. 这个断点打在哪里呢?就是NioEventLoop上的select方法上. 然后我们启动一个客户端. 然后我们debug看到,selectedKey的数量 = 1,说明有accept或者读写等事件发生. 接下就会进 processSelectedKeys() 我们上一节讲到,这里的attach就是NioServerSocketChannel, 我们进入 processSelectedKey(

4. Netty源码分析之Unsafe

Unsafe类实际上是Channel接口的辅助类,实际的IO操作都是由Unsafe接口完成的. 一.Unsafe继承关系图 二.AbstractUnsafe源码分析 1. register方法 register方法主要用于将当前Unsafe对应的Channel注册到EventLoop的多路复用器上,然后调用DefaultChannelPipeline的fireChannelRegisted方法,如果Channel被激活,则调用fireChannelActive方法. public final v

Netty源码分析第3章(客户端接入流程)---->第4节: NioSocketChannel注册到selector

Netty源码分析第三章: 客户端接入流程 第四节: NioSocketChannel注册到selector 我们回到最初的NioMessageUnsafe的read()方法: public void read() { //必须是NioEventLoop方法调用的, 不能通过外部线程调用 assert eventLoop().inEventLoop(); //服务端channel的config final ChannelConfig config = config(); //服务端channel

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

Netty源码分析第二章: NioEventLoop 第六节: 执行select操作 分析完了selector的创建和优化的过程, 这一小节分析select相关操作 跟到跟到NioEventLoop的run方法: protected void run() { for (;;) { try { switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) { case SelectStrategy.CONTINUE

netty 源码分析二

以服务端启动,接收客户端连接整个过程为例分析, 简略分为 五个过程: 1.NioServerSocketChannel 管道生成, 2.NioServerSocketChannel 管道完成初始化, 3.NioServerSocketChannel注册至Selector选择器, 4.NioServerSocketChannel管道绑定到指定端口,启动服务 5.NioServerSocketChannel接受客户端的连接,进行相应IO操作 Ps:netty内部过程远比这复杂,简略记录下方便以后回忆

netty源码分析之揭开reactor线程的面纱(二)

如果你对netty的reactor线程不了解,建议先看下上一篇文章netty源码分析之揭开reactor线程的面纱(一),这里再把reactor中的三个步骤的图贴一下 reactor线程 我们已经了解到netty reactor线程的第一步是轮询出注册在selector上面的IO事件(select),那么接下来就要处理这些IO事件(process selected keys),本篇文章我们将一起来探讨netty处理IO事件的细节 我们进入到reactor线程的 run 方法,找到处理IO事件的代

Netty源码分析第2章(NioEventLoop)---->第7节: 处理IO事件

Netty源码分析第二章: NioEventLoop 第七节:处理IO事件 上一小节我们了解了执行select()操作的相关逻辑, 这一小节我们继续学习select()之后, 轮询到io事件的相关逻辑: 回到NioEventLoop的run()方法: protected void run() { for (;;) { try { switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) { case Sele

Netty源码分析第3章(客户端接入流程)---->第5节: 监听读事件

Netty源码分析第三章: 客户端接入流程 第五节: 监听读事件 我们回到AbstractUnsafe的register0()方法: private void register0(ChannelPromise promise) { try { //省略代码 //做实际的注册 doRegister(); neverRegistered = false; registered = true; //触发事件 pipeline.invokeHandlerAddedIfNeeded(); safeSetS