Netty Source Analysis - --- NioEventLoop Group

Keywords: Java Netty socket Attribute

When it comes to Netty, it must be the thread model that supports it to withstand high concurrency. When it comes to the thread model, we have to mention NioEventLoopGroup, the thread pool, and then get to the point.

Thread model

Let's start with an example of Netty usage

package com.wrh.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public final class SimpleServer {

    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new SimpleServerHandler())
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                        }
                    });

            ChannelFuture f = b.bind(8888).sync();

            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    private static class SimpleServerHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("channelActive");
        }

        @Override
        public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
            System.out.println("channelRegistered");
        }

        @Override
        public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
            System.out.println("handlerAdded");
        }
    }
}

Next, I'll analyze the first and second lines of code to see what the constructor of the NioEventLoopGroup class does. The rest will be analyzed in other blog posts.

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();

As you can see from the code, two thread pools, bossGroup and workerGroup, are used here, so why do you need to define two thread pools? This brings us to Netty's thread model.

 

 

Netty's thread model is called Reactor model. As shown in the figure, the main Reactor refers to the bossGroup, which handles connection requests from clients and registers accept connections to one of the threads of the subReactor. The subReactor on the graph, of course, refers to the worker group, which handles established connections. Data read and write on the client channel; there is also a ThreadPool on the diagram which is a specific thread pool for business logic processing. Generally, subReactor can be reused. This is the use in my project, but the official recommendation is to use a separate ThreadPool when dealing with some more time-consuming business.

NioEventLoopGroup constructor

The code for the constructor of NioEventLoopGroup is as follows

public NioEventLoopGroup() {
    this(0);
}

public NioEventLoopGroup(int nThreads) {
    this(nThreads, null);
}

public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory) {
    this(nThreads, threadFactory, SelectorProvider.provider());
}

public NioEventLoopGroup(
        int nThreads, ThreadFactory threadFactory, final SelectorProvider selectorProvider) {
    super(nThreads, threadFactory, selectorProvider);
} 

The constructors in the NioEventLoopGroup class are ultimately the constructors of the parent class MultithreadEventLoopGroup as follows:

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

From the above constructor, you can see that if you use EventLoop Group workerGroup = new NioEventLoopGroup () to create objects, that is, you do not specify the number of threads, netty gives us the default number of threads, and if you specify, you use the number of threads we specify.

The code associated with the number of default threads is as follows:

static {
    DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
            "io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));

    if (logger.isDebugEnabled()) {
        logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
    }
}

The function of SystemPropertyUtil.getInt function is to get the value corresponding to the specified key in the system attribute (here: key="io.netty.eventLoopThreads"), and return the default value if the acquisition fails. The default value here is two times the number of cores of the cpu.

CONCLUSION: If the program startup parameter is not set (or the attribute value of key="io.netty.eventLoopThreads" is not specified), the number of threads by default is multiplied by the core of the cpu by 2.

Keep looking, since the constructor of MultithreadEventLoopGroup is the constructor of its parent MultithreadEventExecutorGroup, look at this class of constructors

protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
    if (nThreads <= 0) {
        throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
    }

    if (threadFactory == null) {
        threadFactory = newDefaultThreadFactory();
    }

    children = new SingleThreadEventExecutor[nThreads];
    //According to whether the number of threads is the power of 2 or not, different strategies are used to initialize them. chooser
    if (isPowerOfTwo(children.length)) {
        chooser = new PowerOfTwoEventExecutorChooser();
    } else {
        chooser = new GenericEventExecutorChooser();
    }
        //produce nTreads individual NioEventLoop Objects are saved in children In arrays
    for (int i = 0; i < nThreads; i ++) {
        boolean success = false;
        try {
            children[i] = newChild(threadFactory, 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 newChild If the method fails to execute, it executes on the previous one. new Several Successful NioEventLoop Conduct shutdown Handle
            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) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
            }
        }
    }
}

The constructor does the following three things:

1. A threadFactory is generated: threadFactory = new DefaultThreadFactory ();

MultithreadEventExecutorGroup.java
protected ThreadFactory newDefaultThreadFactory() {
    return new DefaultThreadFactory(getClass());//getClass()For: NioEventLoopGroup.class
}

DefaultThreadFactory.java    
public DefaultThreadFactory(Class<?> poolType) {
    this(poolType, false, Thread.NORM_PRIORITY);
}

2. Different strategies are used to initialize chooser according to whether the number of threads is the power of 2 or not.

private static boolean isPowerOfTwo(int val) {
    return (val & -val) == val;
}

3. The NioEventLoop objects generated by nTreads are stored in the children array. Threads are generated by calling the newChild method.

@Override
protected EventExecutor newChild(
        ThreadFactory threadFactory, Object... args) throws Exception {
    return new NioEventLoop(this, threadFactory, (SelectorProvider) args[0]);
}

The parameters passed to the NioEventLoop constructor here are: NioEventLoop Group, DefaultThreadFactory, Selector Provider.

Analysis of NioEventLoop Constructor

Now that we mentioned a new NioEventLoop object, let's look at this class and its parent class.

NioEventLoop(NioEventLoopGroup parent, ThreadFactory threadFactory, SelectorProvider selectorProvider) {
    super(parent, threadFactory, false);
    if (selectorProvider == null) {
        throw new NullPointerException("selectorProvider");
    }
    provider = selectorProvider;
    selector = openSelector();
}

Continue to look at the constructor of the parent SingleThreadEventLoop

protected SingleThreadEventLoop(EventLoopGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp) {
    super(parent, threadFactory, addTaskWakesUp);
}

Another direct call to the constructor of the parent SingleThreadEventExecutor

protected SingleThreadEventExecutor(
        EventExecutorGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp) {

    if (threadFactory == null) {
        throw new NullPointerException("threadFactory");
    }

    this.parent = parent;
    this.addTaskWakesUp = addTaskWakesUp;//false

    thread = threadFactory.newThread(new Runnable() {
        @Override
        public void run() {
            boolean success = false;
            updateLastExecutionTime();
            try {
            //call NioEventLoop Class run Method
                SingleThreadEventExecutor.this.run();
                success = true;
            } catch (Throwable t) {
                logger.warn("Unexpected exception from an event executor: ", t);
            } finally {
                for (;;) {
                    int oldState = STATE_UPDATER.get(SingleThreadEventExecutor.this);
                    if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
                            SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
                        break;
                    }
                }
                // Check if confirmShutdown() was called at the end of the loop.
                if (success && gracefulShutdownStartTime == 0) {
                    logger.error(
                            "Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +
                            SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must be called " +
                            "before run() implementation terminates.");
                }

                try {
                    // Run all remaining tasks and shutdown hooks.
                    for (;;) {
                        if (confirmShutdown()) {
                            break;
                        }
                    }
                } finally {
                    try {
                        cleanup();
                    } finally {
                        STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
                        threadLock.release();
                        if (!taskQueue.isEmpty()) {
                            logger.warn(
                                    "An event executor terminated with " +
                                    "non-empty task queue (" + taskQueue.size() + ')');
                        }

                        terminationFuture.setSuccess(null);
                    }
                }
            }
        }
    });

    taskQueue = newTaskQueue();
} 
protected Queue<Runnable> newTaskQueue() {
    return new LinkedBlockingQueue<Runnable>();
}

The main tasks are as follows:

1. Using ThreadFactory to create a Thread and pass in a Runnable object, the Runnable rewrites a long run code, but the emphasis is only on calling the run method of the NioEventLoop class.

2. Initialize taskQueue using the LinkedBlockingQueue class.

The code for the newThread method is as follows:

DefaultThreadFactory.java

@Override
public Thread newThread(Runnable r) {
    Thread t = newThread(new DefaultRunnableDecorator(r), prefix + nextId.incrementAndGet());

    try {
    //Determine whether it is a daemon thread and set it up
        if (t.isDaemon()) {
            if (!daemon) {
                t.setDaemon(false);
            }
        } else {
            if (daemon) {
                t.setDaemon(true);
            }
        }
            //Set its priority
        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(r, name);
}

FastThreadLocalThread.java

public FastThreadLocalThread(Runnable target, String name) {
    super(target, name);// FastThreadLocalThread extends Thread 
} 

Here, you can see whether threads are created at the bottom or in a way similar to Thread thread = new Thread(r).

There are four properties initialized for the NioEventLoop object.

1. NioEventLoopGroup (in parent SingleThreadEventExecutor)

2,selector

3,provider

4. thread (in the parent SingleThreadEventExecutor)

summary

For the NioEventLoop Group, summarize as follows

1. If the number of threads is not specified, the number of threads is: CPU's core*2

2. Different strategies are used to initialize chooser according to whether the number of threads is the power of 2 or not.

3. Generating nThreads NioEventLoop objects is stored in the children array.

You can understand that NioEventLoop is a thread. There are several attributes in the thread NioEventLoop:

1. NioEventLoopGroup (in parent SingleThreadEventExecutor)

2,selector

3,provider

4. thread (in the parent SingleThreadEventExecutor)

More popular: NioEventLoop Group is a thread pool, and NioEventLoop is a thread. There are N NioEventLoop threads in the NioEventLoop Group thread pool.

Posted by jackwh on Wed, 04 Sep 2019 19:16:10 -0700