netty源码解析---selector.run()

本文解析Netty的Selector选择器在任务队列空闲与繁忙时的工作流程,涉及busy_wait、select_now和任务调度细节,揭示了关键方法hasTasks的决策作用及selector的重建策略。
摘要由CSDN通过智能技术生成

概述

本文将对netty的选择器的工作原理进行解析

run()

直接步入正题!!!

protected void run() {
        for (;;) {
            try {
                try {
                    switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                    case SelectStrategy.CONTINUE:
                        continue;
                    case SelectStrategy.BUSY_WAIT:
                    case SelectStrategy.SELECT:
                        select(wakenUp.getAndSet(false));
                        if (wakenUp.get()) {
                            selector.wakeup();
                        }
                    default:
                    }
                } catch (IOException e) {
                    rebuildSelector0();
                    handleLoopException(e);
                    continue;
                }
                cancelledKeys = 0;
                needsToSelectAgain = false;
                final int ioRatio = this.ioRatio;
                if (ioRatio == 100) {
                    try {
                        processSelectedKeys();
                    } finally {
                        runAllTasks();
                    }
                } else {
                    final long ioStartTime = System.nanoTime();
                    try {
                        processSelectedKeys();
                    } finally {
                        final long ioTime = System.nanoTime() - ioStartTime;
                        //3.处理任务队列
                        runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                }
            } catch (Throwable t) {
                handleLoopException(t);
            }
            try {
                if (isShuttingDown()) {
                    closeAll();
                    if (confirmShutdown()) {
                        return;
                    }
                }
            } catch (Throwable t) {
                handleLoopException(t);
            }
        }
    }

在这个switch中
SelectStrategy.CONTINUE://-2
SelectStrategy.BUSY_WAIT://-3
SelectStrategy.SELECT://-1
hasTasks():这个方法就是去判断当前任务队列里面是不是有任务,如果有任务,返回的是一个正数,所以不会运行到switch里面的代码,如果没有任务,当一切准备就绪的话switch里面的值就是 -1
所以,如果队列里面有任务,对于boss线程而言是优先去处理任务

来看看 没有任务的情况

private void select(boolean oldWakenUp) throws IOException {
        Selector selector = this.selector;
        try {
            int selectCnt = 0;
            long currentTimeNanos = System.nanoTime();
            long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);

            for (;;) {
                long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
                if (timeoutMillis <= 0) {
                    if (selectCnt == 0) {
                        selector.selectNow();
                        selectCnt = 1;
                    }
                    break;
                }

                if (hasTasks() && wakenUp.compareAndSet(false, true)) {
                    selector.selectNow();
                    selectCnt = 1;
                    break;
                }
                int selectedKeys = selector.select(timeoutMillis);
                selectCnt ++;

                if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
                    break;
                }
                if (Thread.interrupted()) {
	               ...
                    selectCnt = 1;
                    break;
                }

                long time = System.nanoTime();
                //现在的时间-select阻塞的时间=>运行之前的时间
                if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
                    // timeoutMillis在没有选择任何内容的情况下运行。
                    selectCnt = 1;
                } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
                        selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
 
                    selector = selectRebuildSelector(selectCnt);
                    selectCnt = 1;
                    break;
                }
                currentTimeNanos = time;
            }

            if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {
               ...
            }
        } catch (CancelledKeyException e) {
            ...
        }
    }
	 	 Selector selector = this.selector;
            int selectCnt = 0;
            long currentTimeNanos = System.nanoTime();
            long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);

获取selector选择器
并且获取当前系统的时间
selectDeadLineNanos这个不做过多讲解,这里笔者直接给出结论
在dalayNanos中,netty回去查看当前是否有定时任务
有定时任务,selectDeadLineNanos就是定时任务开始执行的时间
没有定时任务,selectDeadLineNanos就是currentTimeNanos+1

然后就会进入一个死循环,在循环里又开始对时间进行判断

                long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
                if (timeoutMillis <= 0) {
                    if (selectCnt == 0) {
                        selector.selectNow();
                        selectCnt = 1;
                    }
                    break;
                }

用上面得出的 selectDeadLineNanos ,currentTimeNanos (当前系统时间)
其实也就是说,当执行的定时任务小于0.5或者没有定时任务的时候会进这个 if ,然后机会break;
所以这个select就会返回,然后就退到run()去执行任务队列了

                if (hasTasks() && wakenUp.compareAndSet(false, true)) {
                    selector.selectNow();
                    selectCnt = 1;
                    break;
                }

上面这段代码,第一个判断是判断有没有任务,如果说有任务,就将wakeUp这个变量设置为true
这个wakeup是可能被修改的,例如main线程就可能进行修改,在添加任务的execute的方法中,调用 addTask(task);后,最下面还有
一段代码,如下

		if (!addTaskWakesUp && wakesUpForTask(task)) {
            wakeup(inEventLoop);
        }
        protected void wakeup(boolean inEventLoop) {
	        if (!inEventLoop && wakenUp.compareAndSet(false, true)) {
	            selector.wakeup();
        }
    }

这里的wakeup比较复杂需要分好几种情况分析,笔者先使用found usages来看看哪里修改了这个wakeup
在这里插入图片描述修改为false,每次调用select之前都会先将这个变量设置为false
另外就是两处不同的compareAndSet(false,true)
在这里,图上796的 compareAndSet(false,true) 是reactor线程修改的
745 是其他添加队列线程进行修改的(以main线程举例)
情况一:提交任务时wakeup已经为true,那说明一定是有任务的,所以这个任务会排队执行,所以不会执行到wakeup
情况二:没有快要执行的定时任务,main线程先修改wakeup,reactor后修改
main线程修改成功,成功调用selector的wakeup(),reactor的wakeup()失败,但是selector.select(timeoutMillis)不
会阻塞,后续判断成功break;
情况三:没有快要执行的定时任务,reactor线程先修改wakeup,main后修改
reactor线程修改成功,reactor成功就会直接去break去执行队列任务,那么main失败就不会调用wakeup()
因为有两个地方都需要修改wakeup这个变量,所以用了带有expect的cas,保证这个wakeup()只会执行一次,因为这个是个耗时的操作

那么为什么需要两个地方修改呢???值在select里面修改不行么???
不行,因为select(time)这个方法是一个阻塞的操作,所以执行到这里reactor会在这里阻塞,但是netty提供了另外一个地方修改,就是通过添加任务的线程修改wakeup,从而唤醒selector,让其去执行任务

继续向下分析

				int selectedKeys = selector.select(timeoutMillis);
                selectCnt ++;

                if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
                    break;
                }
                long time = System.nanoTime();
                if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
                    selectCnt = 1;
                } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
                        selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
                    selector = selectRebuildSelector(selectCnt);
                    selectCnt = 1;
                    break;
                }
                currentTimeNanos = time;

什么情况下selectedKeys 会==0?
1.空轮询,select不会阻塞,一直返回0
2.没有客户端连接

这段代码中首先获取系统时间time ,然后减去timeoutMillis(select的阻塞时间),currentTimeNanos可以理解为定时任务的执行时间,如果说成立,就说明执行的时间在currentTimeNacos之后,说明没有发生空轮询,或者说空轮询时间很短,不做处理,但是如果不成立,则说明netty进行了一段时间的空轮询,就会将,如果说空轮询的次数大于SELECTOR_AUTO_REBUILD_THRESHOLD(默认512)就会将selector进行替换,对新的selector进行重构
这些操作都在selectRebuildSelector(),主要做的就是三件事
1.创建一个新的selector
2.将原来的selector中注册的事件取消
3.将可用事件重新注册到新的selector,激活

2023-07-14 15:19:01.215 WARN 7308 --- [sson-netty-2-15] io.netty.util.concurrent.DefaultPromise : An exception was thrown by org.redisson.misc.RedissonPromise$$Lambda$888/0x00000008008f7440.operationComplete() java.lang.NullPointerException: null 2023-07-14 15:19:01.216 ERROR 7308 --- [sson-netty-2-15] o.r.c.SentinelConnectionManager : Can't execute SENTINEL commands on /172.24.107.11:26379 org.redisson.client.RedisException: ERR No such master with that name. channel: [id: 0x2d66827d, L:/172.23.9.103:46812 - R:/172.24.107.11:26379] command: (SENTINEL SLAVES), params: [mymaster] at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:365) ~[redisson-3.13.3.jar:3.13.3] at org.redisson.client.handler.CommandDecoder.decodeCommand(CommandDecoder.java:196) ~[redisson-3.13.3.jar:3.13.3] at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:134) ~[redisson-3.13.3.jar:3.13.3] at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:104) ~[redisson-3.13.3.jar:3.13.3] at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:501) ~[netty-codec-4.1.51.Final.jar:4.1.51.Final] at io.netty.handler.codec.ReplayingDecoder.callDecode(ReplayingDecoder.java:366) ~[netty-codec-4.1.51.Final.jar:4.1.51.Final] at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276) ~[netty-codec-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) ~[netty-common-4.1.51.Final.jar:4.1.51.Final] at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.51.Final.jar:4.1.51.Final] at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.51.Final.jar:4.1.51.Final] at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na] 解决方法
07-15
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值