跟踪源码剖析netty中的线程管理

学习netty,一项不可绕过的知识点就是其线程模型。我们知道一个EventLoop包含一个线程,EventLoopGroup管理一组线程。本文基于netty4.1.26探访何时创建、如何管理这些线程。本文以io.netty.channel.nio包下的NioEventLoopGroup类为源头,跟踪代码,忽略与线程无关的逻辑和参数。Let's go!

首先,我们调用new NioEventLoopGroup()会进入下面的逻辑:

    public NioEventLoopGroup() {
        this(0);      //转到下面的的调用,nThreads默认为0
    }

    public NioEventLoopGroup(int nThreads) {
        this(nThreads, (Executor) null);      //转到下面的的调用,executor默认为null
    }  

    public NioEventLoopGroup(int nThreads, Executor executor) {
        this(nThreads, executor, SelectorProvider.provider());    //继续调用下面的构造函数,这里的默认参数先不管
    }

    public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider) {
        this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);   //同样,继续调用下面
    }

    public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
                             final SelectStrategyFactory selectStrategyFactory) {
        //这里将调用超类的构造函数
        super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
    }

这样就进入了io.netty.channel包下的MultithreadEventLoopGroup类:


    protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
    }

在这里将进入超类io.netty.util.concurrent包下的MultithreadEventExecutorGroup类,第一个参数已经不再是0,更改成了由系统参数获得的大于等于1的数,即表示的管理的线程个数(究竟是多少暂且不用关心),继续:


    protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
        //调用下面的函数
        this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
    }

    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) {
            //这里是关键,用于每个EventLoop对应一个线程的关键
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }
        ...

这里executor不再为null,更改为了io.netty.util.concurrent包下的ThreadPerTaskExecutor,我们来看一下代码:


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) {
        //这里每执行一次execute创建一个线程去执行
        threadFactory.newThread(command).start();
    }
}

那么这里executor为ThreadPerTaskExecutor类的实例有什么用呢?继续看MultithreadEventExecutorGroup构造函数剩余的部分:


        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 {
               ...
            }
        }

children为MultithreadEventExecutorGroup类的属性:


public abstract class MultithreadEventExecutorGroup extends AbstractEventExecutorGroup {

    private final EventExecutor[] children;

来看一下newChild方法:


    protected abstract EventExecutor newChild(Executor executor, Object... args) throws Exception;

这里是抽象方法,对于不同子类有不同实现。所以回到最开始的NioEventLoopGroup类中寻找到该方法的实现:


    @Override
    protected EventLoop newChild(Executor executor, Object... args) throws Exception {
        return new NioEventLoop(this, executor, (SelectorProvider) args[0],
            ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
    }

所以NioEventLoopGroup类管理的EventLoop为实际是io.netty.channel.nio包下的NioEventLoop类。下面我们重点看这个类及其超类的代码:


    NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
                 SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
        super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
        ...
    }

进入io.netty.channel包下的SingleThreadEventLoop类:


    protected SingleThreadEventLoop(EventLoopGroup parent, Executor executor,
                                    boolean addTaskWakesUp, int maxPendingTasks,
                                    RejectedExecutionHandler rejectedExecutionHandler) {
        super(parent, executor, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler);
        ...
    }

进入io.netty.util.concurrent包下的SingleThreadEventExecutor类:


    protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
                                        boolean addTaskWakesUp, int maxPendingTasks,
                                        RejectedExecutionHandler rejectedHandler) {
        ...
        //executor如果为null,抛出异常,否则赋值给属性executor 
        this.executor = ObjectUtil.checkNotNull(executor, "executor");
        ...
    }

至此,new NioEventLoopGroup()构造函数已经完结。这里重点关注的是这个executor属性,就是那个ThreadPerTaskExecutor类的实例。那么,究竟怎么让NioEventLoop创建一个工作线程呢?我们在io.netty.util.concurrent包下的SingleThreadEventExecutor类中找到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);
                }
            }
        }
    }

    private void doStartThread() {
        //在调用这个方法时,要求管理的thread属性为null,表示还没有创建线程
        assert thread == null;
        //可以追溯到ThreadPerTaskExecutor的这个executor,每执行一次executor.execute就创建一个线程,
        //在整个本类中,只有这个地方会调用executor.execute方法
        executor.execute(new Runnable() {
            @Override
            public void run() {
                //将属性赋值上,表示已经创建了线程
                thread = Thread.currentThread();
                if (interrupted) {
                    thread.interrupt();
                }
                boolean success = false;
                updateLastExecutionTime();
                try {
                    //这里也是关键,调用外部类的run方法,这里暂且不表,后面分析
                    SingleThreadEventExecutor.this.run();
                    success = true;
                } catch (Throwable t) {
                    logger.warn("Unexpected exception from an event executor: ", t);
                } finally {
                    ...
                }
            }
        });
    }

这个私有的startThread()方法只在该类的execute方法中被调用,我们来看一下:


    @Override
    public void execute(Runnable task) {
        if (task == null) {
            throw new NullPointerException("task");
        }
        //当前执行此代码的线程是否是thread属性所指的线程
        boolean inEventLoop = inEventLoop();
        //将任务放入队列,以待执行
        addTask(task);
        if (!inEventLoop) {
            startThread();
            if (isShutdown() && removeTask(task)) {
                reject();
            }
        }

        if (!addTaskWakesUp && wakesUpForTask(task)) {
            wakeup(inEventLoop);
        }
    }

    //这个方法其实在这个类的超类中,这里拿到一起分析
    @Override
    public boolean inEventLoop() {
        return inEventLoop(Thread.currentThread());
    }
 
    @Override
    public boolean inEventLoop(Thread thread) {
        return thread == this.thread;
    }

至于execute方法在哪个线程被调用,这涉及更大范围的代码,这里暂且不研究。我们可以推测,要不在thread属性所指的线程上执行这个方法,要不在初始阶段还没有创建线程时候在其他线程执行这个方法。不可能在已经创建了线程的情况下,在外部线程执行这个execute方法,因为doStartThread()方法一开始有assert thread == null。

最后一个问题,SingleThreadEventExecutor.this.run()调用究竟回到了哪里??其实回到了io.netty.channel.nio包下的NioEventLoop类,我们看一下其run方法:


    @Override
    protected void run() {
        for (;;) {
            try {
                switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                    case SelectStrategy.CONTINUE:
                        continue;
                    case SelectStrategy.SELECT:
                        select(wakenUp.getAndSet(false));

                        if (wakenUp.get()) {
                            selector.wakeup();
                        }
                        // fall through
                    default:
                }

                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);
            }
        }
    }

这里的代码看看就好了,无限循环,获取事件并处理事件。有机会再研究。

总结:本文从代码跟踪的视角关注EventLoop如何管理一个线程,抛开了无关的因素,了解netty线程模型的内涵。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值