netty(二):事件执行分析
来源:互联网 发布:阿里云客服一天多少钱 编辑:程序博客网 时间:2024/06/01 22:00
上一篇中,重点是看下自己对各个组件的认识,数据流向的大致把握。
我记得,我经常问自己,事件到底是怎么被执行的呢?我怎么看事件被执行,被哪个线程执行的呢?
还有一个问题:vertx中,译者注如下:
每个 Verticle
在部署的时候都会被分配一个 Context
(根据配置不同,可以是Event Loop Context 或者 Worker Context),之后此 Verticle
上所有的普通代码都会在此 Context
上执行(即对应的 Event Loop 或Worker 线程)。一个 Context
对应一个 Event Loop 线程(或 Worker 线程),但一个 Event Loop 可能对应多个 Context
。
一直觉得一个Context对应一个EventLoop,自然一个EventLoop就对应一个Context。既然理解有偏差,肯定是netty的概念还存在混淆,于是。
1.EventLoopGroup与EventLoop的关系。
先看一段服务端启动的代码:
// 配置服务端的NIO线程组EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .childHandler(new ChildChannelHandler()); // 绑定端口,同步等待成功 ChannelFuture f = b.bind(port).sync(); // 等待服务器坚挺端口关闭 f.channel().closeFuture().sync();} finally { // 优雅退出,释放线程池资源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully();}
这段代码我们之前看过,public NioEventLoopGroup() { this(0);}
/** * Create a new instance. * * @param nThreads the number of threads that will be used by this instance. * @param executor the Executor to use, or {@code null} if the default should be used. * @param chooserFactory the {@link EventExecutorChooserFactory} to use. * @param args arguments which will passed to each {@link #newChild(Executor, Object...)} call */protected MultithreadEventExecutorGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory, Object... args) { if (nThreads <= 0) { throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads)); } if (executor == null) { executor = new ThreadPerTaskExecutor(newDefaultThreadFactory()); } 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) { // TODO: Think about if this is a good exception type throw new IllegalStateException("failed to create a child event loop", e); } finally { if (!success) { for (int j = 0; j < i; j ++) { children[j].shutdownGracefully(); } for (int j = 0; j < i; j ++) { EventExecutor e = children[j]; try { while (!e.isTerminated()) { e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS); } } catch (InterruptedException interrupted) { // Let the caller handle the interruption. Thread.currentThread().interrupt(); break; } } } } } chooser = chooserFactory.newChooser(children); 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); } Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length); Collections.addAll(childrenSet, children); readonlyChildren = Collections.unmodifiableSet(childrenSet);}
对照上面的代码,我们知道NioEventLoopGroup在初始化的时候,executor默认用的是ThreadPerTaskExecutor这个任务执行器,初始化的时候传入了一个默认的线程工厂。这个先不说。
NioEventLoopGroup继承自MultiThreadEventEexcutorGroup
private final EventExecutor[] children;MultiXXXGroup这个类中有EventExecutor数组,如果NioEventLoopGroup上管理EventLoop的,那么,这个数组就应该对应到EventLoop
children[i] = newChild(executor, args);
/** * Create a new EventExecutor which will later then accessible via the {@link #next()} method. This method will be * called for each thread that will serve this {@link MultithreadEventExecutorGroup}. * */protected abstract EventExecutor newChild(Executor executor, Object... args) throws Exception;
在该类中,初始化EventExecutor的newChild方法,并不是自己实现的,而是子类自己去实现,那好,我们跳回到NioEventLoopGroup中@Overrideprotected EventLoop newChild(Executor executor, Object... args) throws Exception { return new NioEventLoop(this, executor, (SelectorProvider) args[0], ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);}
确实,根据传入的执行器,生成新的NioEventLoop对象。2.EventLoop与Thread的关系。
上一篇我们分析过NioEventLoop对象,它继承自SingleThreadEventLoop,很明显,netty一直说一个NioEventLoop对应着一条线程,至此我们都没有看到线程Thread,
在其父类SingleThreadEventExecutor中我们才看到Thread的影子:
private final Queue<Runnable> taskQueue;private volatile Thread thread;@SuppressWarnings("unused")private volatile ThreadProperties threadProperties;private final Executor executor;private volatile boolean interrupted;private final Semaphore threadLock = new Semaphore(0);private final Set<Runnable> shutdownHooks = new LinkedHashSet<Runnable>();private final boolean addTaskWakesUp;private final int maxPendingTasks;private final RejectedExecutionHandler rejectedExecutionHandler;
这里截取了该类的部分成员变量。其中我关心的就是Executor, Thread, taskQueue.那么,Thread是在哪里被初始化的呢?
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 { SingleThreadEventExecutor.this.run(); success = true; } catch (Throwable t) { logger.warn("Unexpected exception from an event executor: ", t); } finally { for (;;) { int oldState = state; if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet( SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) { break; } }
在SingleThreadEventExecutor中跟踪thread变量,发现在doStartThread()方法中thread才被赋值,并且为当前线程。很恐怖。
我们前面也多次提到executor变量,这个是由默认值提供生成的,在这里调用了execute()方法.那我们先去看executor到底是什么:
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(); }}
哦,也就是在执行execute方法的时候,用现成工厂去生成一个新线程,并且启动线程,那就是直接运行了run方法,怪不得直接把thread赋值为当前线程。那么这个线程工厂是生成了一个怎么样的线程呢?DefaultThreadFactory没有什么奇怪的,只是设置了线程的优先级啊,名称啊什么的基本信息,真正生成方法是下面这个:
@Overridepublic Thread newThread(Runnable r) { Thread t = newThread(new DefaultRunnableDecorator(r), prefix + nextId.incrementAndGet()); try { if (t.isDaemon() != daemon) { t.setDaemon(daemon); } if (t.getPriority() != priority) { t.setPriority(priority); } } catch (Exception ignored) { // Doesn't matter even if failed to set. } return t;}
protected Thread newThread(Runnable r, String name) { return new FastThreadLocalThread(threadGroup, r, name);}
public FastThreadLocalThread(ThreadGroup group, Runnable target, String name) { super(group, target, name);}
看了这个调用链,发现生成的线程是在原Thread类上做了封装,看注释,是为了性能优化呢(还观察到将Runnable参数直接赋值给Thread的target变量了)。/** * A special {@link Thread} that provides fast access to {@link FastThreadLocal} variables. */public class FastThreadLocalThread extends Thread {
这样看来,一个NioEventLoop包含了
Executor-> ThreadPerTaskExecutor执行器
Thread -> FastThreadLocalThread线程
3.事件与Thread的关系。
假设SingleThreadEventExecutor的doStartThread()方法被调用了,这里生成新线程,并开始EventLoop的工作,但是工作在哪儿?我们看到
SingleThreadEventExecutor.this.run();
在线程赋值后被执行了,进去protected abstract void run();这里没有实现,那在上层找找
@Overrideprotected void run() { for (;;) { try { switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) { case SelectStrategy.CONTINUE: continue; case SelectStrategy.SELECT: select(wakenUp.getAndSet(false)); // 'wakenUp.compareAndSet(false, true)' is always evaluated // before calling 'selector.wakeup()' to reduce the wake-up // overhead. (Selector.wakeup() is an expensive operation.) // // However, there is a race condition in this approach. // The race condition is triggered when 'wakenUp' is set to // true too early. // // 'wakenUp' is set to true too early if: // 1) Selector is waken up between 'wakenUp.set(false)' and // 'selector.select(...)'. (BAD) // 2) Selector is waken up between 'selector.select(...)' and // 'if (wakenUp.get()) { ... }'. (OK) // // In the first case, 'wakenUp' is set to true and the // following 'selector.select(...)' will wake up immediately. // Until 'wakenUp' is set to false again in the next round, // 'wakenUp.compareAndSet(false, true)' will fail, and therefore // any attempt to wake up the Selector will fail, too, causing // the following 'selector.select(...)' call to block // unnecessarily. // // To fix this problem, we wake up the selector again if wakenUp // is true immediately after selector.select(...). // It is inefficient in that it wakes up the selector for both // the first case (BAD - wake-up required) and the second case // (OK - no wake-up required). if (wakenUp.get()) { selector.wakeup(); } default: // fallthrough } cancelledKeys = 0; needsToSelectAgain = false; final int ioRatio = this.ioRatio; if (ioRatio == 100) { try { processSelectedKeys(); } finally { // Ensure we always run tasks. runAllTasks(); } } else { final long ioStartTime = System.nanoTime(); try { processSelectedKeys(); } finally { // Ensure we always run tasks. final long ioTime = System.nanoTime() - ioStartTime; runAllTasks(ioTime * (100 - ioRatio) / ioRatio); } } } catch (Throwable t) { handleLoopException(t); } // Always handle shutdown even if the loop processing threw an exception. try { if (isShuttingDown()) { closeAll(); if (confirmShutdown()) { return; } } } catch (Throwable t) { handleLoopException(t); } }}
果然在NioEventLoop中找到了,这段代码在上一篇我们也看到过,就是轮询多路复用器,处理i/o事件的,这样想来,EventLoop线程启动后,在无线循环中select()完成的 SelectionKeys,并封装成任务,被分别处理。处理被选中的key:processSelectedKeys()
private void processSelectedKeys() { if (selectedKeys != null) { processSelectedKeysOptimized(); } else { processSelectedKeysPlain(selector.selectedKeys()); }}
如果selectedKeys不为空就调用优化过的SelectionKeys的处理方法,不然就处理普通的SelectionKeys集合,普通的SelectionKey容器为Set,优化过的容器用数组存储,具体可 查代码。
private void processSelectedKeysPlain(Set<SelectionKey> selectedKeys) { // check if the set is empty and if so just return to not create garbage by // creating a new Iterator every time even if there is nothing to process. // See https://github.com/netty/netty/issues/597 if (selectedKeys.isEmpty()) { return; } Iterator<SelectionKey> i = selectedKeys.iterator(); for (;;) { final SelectionKey k = i.next(); final Object a = k.attachment(); i.remove(); if (a instanceof AbstractNioChannel) { processSelectedKey(k, (AbstractNioChannel) a); } else { @SuppressWarnings("unchecked") NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a; processSelectedKey(k, task); } if (!i.hasNext()) { break; } if (needsToSelectAgain) { selectAgain(); selectedKeys = selector.selectedKeys(); // Create the iterator again to avoid ConcurrentModificationException if (selectedKeys.isEmpty()) { break; } else { i = selectedKeys.iterator(); } } }}
看下这里
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a; processSelectedKey(k, task);
/** * An arbitrary task that can be executed by {@link NioEventLoop} when a {@link SelectableChannel} becomes ready. * * @see NioEventLoop#register(SelectableChannel, int, NioTask) */public interface NioTask<C extends SelectableChannel>
注释说这种nioTask会在channel的ready状态被nioEventLoop execute().
处理任务的方法:runAllTasks()
/** * Poll all tasks from the task queue and run them via {@link Runnable#run()} method. * * @return {@code true} if and only if at least one task was run */protected boolean runAllTasks() { assert inEventLoop(); boolean fetchedAll; boolean ranAtLeastOne = false; do { fetchedAll = fetchFromScheduledTaskQueue(); if (runAllTasksFrom(taskQueue)) { ranAtLeastOne = true; } } while (!fetchedAll); // keep on processing until we fetched all scheduled tasks. if (ranAtLeastOne) { lastExecutionTime = ScheduledFutureTask.nanoTime(); } afterRunningAllTasks(); return ranAtLeastOne;}
/** * Runs all tasks from the passed {@code taskQueue}. * * @param taskQueue To poll and execute all tasks. * * @return {@code true} if at least one task was executed. */protected final boolean runAllTasksFrom(Queue<Runnable> taskQueue) { Runnable task = pollTaskFrom(taskQueue); if (task == null) { return false; } for (;;) { safeExecute(task); task = pollTaskFrom(taskQueue); if (task == null) { return true; } }}
/** * Try to execute the given {@link Runnable} and just log if it throws a {@link Throwable}. */protected static void safeExecute(Runnable task) { try { task.run(); } catch (Throwable t) { logger.warn("A task raised an exception. Task: {}", task, t); }}
它只是循环执行taskQueue中。现在,我们以一个事件跟踪整个流程:
ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .childHandler(new ChildChannelHandler());// 绑定端口,同步等待成功ChannelFuture f = b.bind(port).sync();
我们就个弄bind()事件的处理流程。/** * Create a new {@link Channel} and bind it. */public ChannelFuture bind(int inetPort) { return bind(new InetSocketAddress(inetPort));}
/** * Create a new {@link Channel} and bind it. */public ChannelFuture bind(SocketAddress localAddress) { validate(); if (localAddress == null) { throw new NullPointerException("localAddress"); } return doBind(localAddress);}
private ChannelFuture doBind(final SocketAddress localAddress) { final ChannelFuture regFuture = initAndRegister(); final Channel channel = regFuture.channel(); if (regFuture.cause() != null) { return regFuture; } if (regFuture.isDone()) { // At this point we know that the registration was complete and successful. ChannelPromise promise = channel.newPromise(); doBind0(regFuture, channel, localAddress, promise); return promise; } else { // Registration future is almost always fulfilled already, but just in case it's not. final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel); regFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { Throwable cause = future.cause(); if (cause != null) { // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an // IllegalStateException once we try to access the EventLoop of the Channel. promise.setFailure(cause); } else { // Registration was successful, so set the correct executor to use. // See https://github.com/netty/netty/issues/2586 promise.registered(); doBind0(regFuture, channel, localAddress, promise); } } }); return promise; }}
private static void doBind0( final ChannelFuture regFuture, final Channel channel, final SocketAddress localAddress, final ChannelPromise promise) { // This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up // the pipeline in its channelRegistered() implementation. channel.eventLoop().execute(new Runnable() { @Override public void run() { if (regFuture.isSuccess()) { channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); } else { promise.setFailure(regFuture.cause()); } } });}
到这里,我们看到eventLoop().execute()这里会生成新的Thread,并执行bind方法。后面的bind方法就不追踪了,涉及到具体的channel和pipeline等。 - netty(二):事件执行分析
- Netty的启动执行过程分析(二)
- netty 事件驱动(二)
- netty事件执行顺序
- Netty 源码分析(二)
- Netty(二):服务端客户端实例分析
- netty 源码分析二
- Netty源码分析(九)—IO事件处理流程
- 自顶向下深入分析Netty(二)--线程模型
- netty(十一)源码分析之ByteBuf 二
- Netty源码分析(二)—客户端初始化
- Netty的启动执行过程分析(一)
- Netty从零开始(二)
- Netty学习(二)-Helloworld Netty
- netty 事件驱动(一)
- netty源码分析(二)-处理请求
- netty源码分析 之二 transport(bootstrap)
- Netty 源码分析之 EventLoop(二) (最重要)
- Impala中的invalidate metadata和refresh
- 浏览器工作原理详解
- 英文论文-城市云脑,基于互联网云脑的智慧城市新架构
- Ubuntu上systemtap安装
- 使用ThreadPoolExecutor,当提交线程超过maximumPoolSize 会阻塞主线程吗?
- netty(二):事件执行分析
- CCF-训练50题-NO.27-挖掘机技术哪家强
- mysql在Linux环境下开启远程访问权限和开放3306端口
- jquery选择器之层级选择器
- Java的特点
- Re-ID: Person Re-identification by Local Maximal Occurrence Representation and Metric Learning 论文解析
- CCF-训练50题-NO.28-到底买不买
- 一步步实现WebServer中间件——Http协议
- jquery选择器之筛选选择器