• Netty核心概念(9)之Future


    1.前言

     第7节讲解JAVA的线程模型中就说到了Future,并解释了为什么可以主线程可以获得线程池任务的执行后结果,变成一种同步状态。秘密就在于Java将所有的runnable和callable任务,统一变成了callable,最终包装成了FutureTask对象,该类实现了Runnable接口和Future接口,所以FutureTask能够被线程执行。最终异步执行过程全部由该类控制逻辑,所以在get的时候锁住了该类,run方法执行的时候释放了锁,这样就满足了能够在异步线程执行完毕获取相关结果的能力。

     本章介绍一下Netty对Future的设计,Netty的声明就是一个异步事件驱动框架,上一节学习了整个线程调度的过程,并在最后给出了前几节的一个综合流程图,虽然图中提到了几种Future,但是没有具体介绍细节,这些将在本节得到解释。

    2.相关概念

    2.1 Future

     虽然Java中已经定义了Future,但是满足不了Netty的需求,所以Netty新写了一个Future接口,继承了JDK的Future。额外方法定义如下图:

     接口主要追加了两个功能:1.增加了判断任务是否成功失败的方法,以及失败获取异常信息;2.增加了任务完成时触发的监听器

    2.2 Promise

     该类继承自Future,自然是增加了额外的功能了:这是一个可写的Future。什么意思呢?通过之前的知识,我们知道Future都是由异步线程控制的,主线程是无法控制线程执行的。Promise的作用就是主线程能够控制一下执行的任务。

      setSuccess():标记任务成功,并触发所有listener。如果任务早就成功或失败,则抛出异常

      trySuccess():同上,但是失败只是返回false,而不是抛出异常

     这里就解释这两个方法,其他方法是覆盖了父接口的方法,确定返回的具体类型而已。根据方法我们也能大体明白可写的含义了。

    3 主要Future详解

    3.1 DefaultChannelPromise

     该类是在注册channel时创建的,SingleThreadEventLoop的register方法。和Java的FutureTask不同,FutureTask是作为一个任务交给线程池,在内部控制任务执行。DefaultChannelPromise则是持有了Channel和EventExecutor二者,在外边处理逻辑。其上层有2个抽象父类:

      1.AbstractFuture:

        该类就实现了get方法,原理是调用了await()方法,await之后唤醒肯定就是任务结束了,判断有无异常,最终返回结果还是抛出异常。

      2.DefaultPromise:

        该类提供了一个Promise应该具备的基本实现。对任务标记结果,触发listener等。其主要有个result,对结果进行CAS操作来判断任务是否完成。

        setSuccess过程如下:1.设置成功的结果;2.触发所有的listener。设置结果主要是CAS更新result字段,然后判断是否有get请求等待任务执行完,直接notifyAll即可。触发listener的过程在于先判断当前线程是否是事件线程中,触发方法必须由EventLoop线程执行,然后就是遍历触发listener的operationComplete方法。

        await过程如下:1.判断是否执行完,执行完直接返回;2.判断线程是否中断,中断抛出异常;3.检查死锁,即wait操作不能在EventLoop的线程中执行;4.如果没执行完,等待者计数,然后wait。

     DefaultChannelPromise相对于DefaultPromise而言只是增加了一个channel字段,其它的方法都是调用父类方法。接下来我们看看register过程是怎么使用这个Promise的吧。

        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
                if (eventLoop == null) {
                    throw new NullPointerException("eventLoop");
                }
                if (isRegistered()) {
                    promise.setFailure(new IllegalStateException("registered to an event loop already"));
                    return;
                }
                if (!isCompatible(eventLoop)) {
                    promise.setFailure(
                            new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
                    return;
                }
    
                AbstractChannel.this.eventLoop = eventLoop;
    
                if (eventLoop.inEventLoop()) {
                    register0(promise);
                } else {
                    try {
                        eventLoop.execute(new Runnable() {
                            @Override
                            public void run() {
                                register0(promise);
                            }
                        });
                    } catch (Throwable t) {
                        logger.warn(
                                "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                                AbstractChannel.this, t);
                        closeForcibly();
                        closeFuture.setClosed();
                        safeSetFailure(promise, t);
                    }
                }
            }
    

     可以看到在注册过程中实际上就是使用setXXX方法来处理相关逻辑的,这个和Java的FutureTask采取了不同的方式。

    3.2 SucceededFuture

     上面讲了一个Promise控制主线程和线程池的同步状态,那个是依靠promise才有的setXXX接口来触发的。那么Future是怎么控制的呢?答案是Future不需要控制,返回Future的时候就已经有结果了,并且返回一定是一个同步过程。

     以SucceededFuture为例。Bootstrap中的doResolveAndConnect0方法有段:final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);其解析成功就会返回带有结果的SucceededFuture。看这个类的sync方法也和Promise的不一样,Promise是await,Future是直接返回。这个可以说明Future和Promise的区别:Future用于同步任务,Promise用于异步任务。不知道Netty为什么会设计成这样,让人会有些疑惑。但是记住这点,再加上Promise的set方法达成的效果,就可以理解Netty的Future了。

    4.总结

     Netty的Future设计采取了和Java的FutureTask不同的设计思路。Java的思路是将Futuren包装成一个任务,这样异步线程执行这个FutureTask的时候,其就可以知道任务的执行状态。Netty将Future扩展成了Promise。Future作为同步方法直接返回的结果类,使用较少。Promise提供了setXXX方法,给异步线程调用该方法告知执行状态。相同的地方在于Promise也必须被异步线程持有,才能使用set方法。

  • 相关阅读:
    混杂模式
    消息队列学习
    item21
    消息队列改写
    socket select模型
    EffectiveC++ Item11
    How to read a PCap file from Wireshark with C++
    winsock select学习
    线程安全与可重入函数
    process explorer 查看句柄或者加载的dll
  • 原文地址:https://www.cnblogs.com/lighten/p/8988989.html
Copyright © 2020-2023  润新知