netty source code-EventLoopGroup

EventLoopGroup is associated with Reactor

We introduced three kinds of Reactor threading models, so what do they have to do with NioEventLoopGroup? In fact, different settings

The way of NioEventLoopGroup corresponds to different Reactor thread models.

1. Single-threaded model, look at the following application code:

EventLoopGroup bossGroup = new NioEventLoopGroup(1); 
ServerBootstrap server = new ServerBootstrap(); 
server.group(bossGroup);

Note that we instantiate a NioEventLoopGroup, and then we call server.group(bossGroup) to set the EventLoopGroup on the server side. Some people may be confused; I remember that when starting the Netty program on the server side, you need to set the bossGroup and workerGroup, why only one bossGroup is set here? In fact, the reason is very simple, ServerBootstrap rewrites the group method:

public ServerBootstrap group(EventLoopGroup group) { 
    return group(group, group); 
}

Therefore, when a group is passed in, the bossGroup and workerGroup are the same NioEventLoopGroup. At this time, because bossGroup and workerGroup are the same NioEventLoopGroup, and the number of NioEventLoopGroup thread pools is only set to 1 thread, that is to say, the Acceptor in Netty and all subsequent client connection IO operations are processed in one thread . So corresponding to Reactor's threading model, when we set NioEventLoopGroup like this, it is equivalent to Reactor's single-threaded model.

2. Multithreading model, let's look at the following application code:

EventLoopGroup bossGroup = new NioEventLoopGroup(128); 
ServerBootstrap server = new ServerBootstrap(); 
server.group(bossGroup);

As can be seen from the above code, we only need to set the parameter of bossGroup to a number greater than 1, which is actually the Reactor multi-threaded model.

3. The master-slave threading model. I believe everyone has already thought of it. The code to implement the master-slave threading model is as follows:

EventLoopGroup bossGroup = new NioEventLoopGroup(); 
EventLoopGroup workerGroup = new NioEventLoopGroup(); 
ServerBootstrap b = new ServerBootstrap(); 
b.group(bossGroup, workerGroup);

bossGroup is the main thread, and the thread in workerGroup is the number of CPU cores multiplied by 2, so the corresponding to the Reactor thread model, we know that the NioEventLoopGroup set in this way is actually the Reactor master-slave multi-thread model.

Instantiation of EventLoopGroup

First, let's take a look at the class structure diagram of EventLoopGroup, as shown in the following figure:

The basic process of NioEventLoopGroup initialization, here we review the sequence diagram:

The basic steps are as follows:

1. EventLoopGroup (actually MultithreadEventExecutorGroup) internally maintains an array of children whose class is EventExecutor, the size of which is nThreads, which initializes a thread pool.

2. If we are instantiating NioEventLoopGroup, if the thread pool size is specified, nThreads is the specified value, otherwise it is CPU

Number of nuclei * 2.

3.In MultithreadEventExecutorGroup, the newChild() abstract method is called to initialize the children array.

4. The abstract method newChild() is actually implemented in NioEventLoopGroup, which returns a NioEventLoop instance.

5. Initialize the main properties of NioEventLoop:

provider: Get the SelectorProvider through the provider() method of SelectorProvider in the NioEventLoopGroup constructor.

selector: In the NioEventLoop constructor, call the selector = provider.openSelector() method to obtain the Selector object.

Task performer EventLoop

NioEventLoop inherits from SingleThreadEventLoop, and SingleThreadEventLoop inherits from SingleThreadEventExecutor. The SingleThreadEventExecutor is an abstraction of the local thread in Netty. It has a Thread thread attribute inside and stores a local Java thread. Therefore, we can simply think that a NioEventLoop is actually bound to a specific thread, and during its life cycle, the bound thread will not change.

The class hierarchy diagram of NioEventLoop is still a bit complicated, but we only need to focus on a few important points. First look at the inheritance chain of NioEventLoop: NioEventLoop->SingleThreadEventLoop->SingleThreadEventExecutor->AbstractScheduledEventExecutor. In AbstractScheduledEventExecutor, Netty implements the schedule function of NioEventLoop, that is, we can run some scheduled tasks by calling the schedule method of a NioEventLoop instance. In SingleThreadEventLoop, the function of task team is implemented. Through it, we can call the execute() method of a NioEventLoop instance to add a task to the task queue, and NioEventLoop will schedule and execute it.

Generally speaking, NioEventLoop is responsible for performing two tasks: the first task is used as an IO thread to perform Channel-related IO operations, including calling Selector to wait for ready IO events, reading and writing data and data processing, etc.; and the second Tasks are used as task queues to execute tasks in taskQueue. For example, the timing tasks submitted by users calling eventLoop.schedule are also executed by this thread.

The instantiation process of NioEventLoop

The running sequence diagram of EventLoop instantiation:

As you can see from the figure above, SingleThreadEventExecutor has a Thread type field named thread, which is the local thread associated with SingleThreadEventExecutor. Let's see where thread is assigned:

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 {
                    //省略代码
        });
    }

In the previous chapter, we analyzed that when SingleThreadEventExecutor starts, it calls the doStartThread() method, and then calls the executor.execute() method to assign the current thread to thread. What is done in this thread is mainly to call the SingleThreadEventExecutor.this.run() method, and because NioEventLoop implements this method, according to the polymorphism, the NioEventLoop.run() method is actually called.

EventLoop and Channel association

In Netty, each Channel has and only one EventLoop associated with it, and their association process is as follows:

From the above figure, we can see that when the AbstractChannel$AbstractUnsafe.register() method is called, the association between Channel and EventLoop is completed. The specific implementation of the register() method is as follows:

@Override
        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            ObjectUtil.checkNotNull(eventLoop, "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);
                }
            }
        }

In the AbstractChannel$AbstractUnsafe.register() method, an EventLoop will be assigned to the eventLoop field in the AbstractChannel. This code is to complete the process of association between EventLoop and Channel.

EventLoop start

As we already know before, NioEventLoop itself is a SingleThreadEventExecutor, so the start of NioEventLoop is actually the start of the local Java thread bound to NioEventLoop.

According to this idea, we only need to find where the start() method of the thread field in SingleThreadEventExecutor is called to know where the thread was started. From the analysis in the previous chapter, in fact, we have already made it clear: thread.start() is encapsulated in the SingleThreadEventExecutor.startThread() method, look at the code:

 private void startThread() {
        if (state == ST_NOT_STARTED) {
            if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
                boolean success = false;
                try {
                    //*
                    doStartThread();
                    success = true;
                } finally {
                    if (!success) {
                        STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
                    }
                }
            }
        }
    }

STATE_UPDATER is an attribute maintained internally by SingleThreadEventExecutor, and its function is to identify the current thread state. At the beginning, STATE_UPDATER == ST_NOT_STARTED, so when the startThread() method is called for the first time, it will enter the if statement and then call the thread.start() method. And where is the key startThread() method called? Using the method call relationship reverse lookup function, we found that startThread is called in the execute() method of SingleThreadEventExecutor:

 private void execute(Runnable task, boolean immediate) {
        boolean inEventLoop = inEventLoop();
        //添加到队列中
        addTask(task);
        if (!inEventLoop) {
            //启动线程
            startThread();
            if (isShutdown()) {
                boolean reject = false;
                try {
                    if (removeTask(task)) {
                        reject = true;
                    }
                } catch (UnsupportedOperationException e) {
                    // The task queue does not support removal so the best thing we can do is to just move on and
                    // hope we will be able to pick-up the task before its completely terminated.
                    // In worst case we will log on termination.
                }
                if (reject) {
                    reject();
                }
            }
        }
        if (!addTaskWakesUp && immediate) {
            wakeup(inEventLoop);
        }
    }

In this case, our job now becomes to find where the execute() method of SingleThreadEventExecutor was called for the first time. If a careful friend may have thought of it, in the previous chapter, we mentioned that in the process of registering a channel, the eventLoop.execute() method will be called in the register() of AbstractChannel$AbstractUnsafe, which will be performed in EventLoop. The execution of Channel registration code, the register() part of AbstractChannel$AbstractUnsafe code is as follows:

@Override
        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            ObjectUtil.checkNotNull(eventLoop, "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);
                }
            }
        }

Obviously, all the way from the bind() method of Bootstrap to the register() method of AbstractChannel$AbstractUnsafe, the entire code runs in the main thread, so the above eventLoop.inEventLoop() returns false, so it enters the else branch , In this branch, the eventLoop.execute() method is called, and NioEventLoop does not implement the execute() method, so the execute() method of SingleThreadEventExecutor is called;

private void execute(Runnable task, boolean immediate) {
        boolean inEventLoop = inEventLoop();
        //添加到队列中
        addTask(task);
        if (!inEventLoop) {
            //启动线程
            startThread();
            if (isShutdown()) {
                boolean reject = false;
                try {
                    if (removeTask(task)) {
                        reject = true;
                    }
                } catch (UnsupportedOperationException e) {
                }
                if (reject) {
                    reject();
                }
            }
        }

        if (!addTaskWakesUp && immediate) {
            wakeup(inEventLoop);
        }
    }

We have analyzed that inEventLoop == false, so the execution reaches the else branch, where the startThread() method is called to start the Java native thread associated with SingleThreadEventExecutor.

In summary: when the execute() of EventLoop is called for the first time, it will trigger the call of the startThread() method, which will cause the Java native thread corresponding to EventLoop to start.

After we complete the sequence diagram in the previous section, we get a complete sequence diagram of the EventLoop startup process:

Guess you like

Origin blog.csdn.net/madongyu1259892936/article/details/111812541