Netty 學習(六):創建 NioEventLoopGroup 的核心源碼說明

Netty 學習(六):創建 NioEventLoopGroup 的核心源碼說明

作者: Grey

原文地址:

博客園:Netty 學習(六):創建 NioEventLoopGroup 的核心源碼說明

CSDN:Netty 學習(六):創建 NioEventLoopGroup 的核心源碼說明

基於 JDK 的 API 自己實現 NIO 編程,需要一個線程池來不斷監聽端口。接收到新連接之後,這條連接上數據的讀寫會在另外一個線程池中進行。

在 Netty 實現的服務端中, 有如下經典代碼

EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
// 設置服務端的線程模型。
// bossGroup 負責不斷接收新的連接,將新的連接交給 workerGroup 來處理。 
b.group(bossGroup, workerGroup)

其中 bossGroup 對應的就是監聽端口的線程池,在綁定一個端口的情況下,這個線程池裏只有一個線程;workerGroup 對應的是連接的數據讀寫的線程。

通過 debug 並設置斷點的方式,我們來查看下創建 NioEventLoopGroup 的核心過程,

在沒有指定線程數的情況下new NioEventLoopGroup()會調用如下構造方法

    public NioEventLoopGroup() {
        this(0);
    }

即傳入 0,然後一路跟下去,發現調用了MultithreadEventLoopGroup的如下邏輯

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

由於我們傳入的nThreads == 0,所以獲取DEFAULT_EVENT_LOOP_THREADS的值,在MultithreadEventLoopGroup中,DEFAULT_EVENT_LOOP_THREADS的初始化邏輯如下

private static final int DEFAULT_EVENT_LOOP_THREADS;

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

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

nThreads == 0的情況下,那麼 NioEventLoopGroup 的默認線程的個數爲 CPU 的核數乘以 2,即:NettyRuntime.availableProcessors() * 2

繼續跟下去,可以看到 NioEventLoopGroup 調用瞭如下的構造方法,其核心代碼如下

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
 ……
 // 創建ThreadPerTaskExecutor:ThreadPerTaskExecutor表示每次調用execute()方法的時候,都會創建一個線程。
        if (executor == null) {
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }
……
// 2.創建NioEventLoop:NioEventLoop對應線程池裏線程的概念,這裏其實就是用一個for循環創建的。
        children = new EventExecutor[nThreads];
……
        for (int i = 0; i < nThreads; i ++) {
            ……
            children[i] = newChild(executor, args);
            ……
        }

// 3.創建線程選擇器:線程選擇器的作用是確定每次如何從線程池中選擇一個線程,也就是每次如何從NioEventLoopGroup中選擇一個NioEventLoop。
        chooser = chooserFactory.newChooser(children);

……
    }

這個構造方法包括了三個內容

  1. 創建 ThreadPerTaskExecutor:ThreadPerTaskExecutor 主要是用來創建線程。

  2. 創建 NioEventLoop:NioEventLoop 對應線程池裏線程的概念。

  3. 創建線程選擇器:線程選擇器的作用是確定每次如何從線程池中選擇一個線程,也就是每次如何從 NioEventLoopGroup 中選擇一個 NioEventLoop。

首先,我們看 ThreadPerTaskExecutor 如何創建線程,核心代碼如下

public final class ThreadPerTaskExecutor implements Executor {
    private final ThreadFactory threadFactory;

    public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
        this.threadFactory = ObjectUtil.checkNotNull(threadFactory, "threadFactory");
    }

    @Override
    public void execute(Runnable command) {
        threadFactory.newThread(command).start();
    }
}

這裏的 threadFactory 就是前面傳入的newDefaultThreadFactory(),這個方法定義了默認線程的一些基本信息,一路追蹤到DefaultThreadFactory

    public DefaultThreadFactory(String poolName, boolean daemon, int priority, ThreadGroup threadGroup) {
        ObjectUtil.checkNotNull(poolName, "poolName");

        if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
            throw new IllegalArgumentException(
                    "priority: " + priority + " (expected: Thread.MIN_PRIORITY <= priority <= Thread.MAX_PRIORITY)");
        }

        prefix = poolName + '-' + poolId.incrementAndGet() + '-';
        this.daemon = daemon;
        this.priority = priority;
        this.threadGroup = threadGroup;
    }

// 創建線程,將 JDK 的 Runnable 包裝成 FastThreadLocalRunnable
        @Override
    public Thread newThread(Runnable r) {
        Thread t = newThread(FastThreadLocalRunnable.wrap(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;
    }

可以看到 Netty 的線程實體是由 ThreadPerTaskExecutor 創建的,ThreadPerTaskExecutor 每次執行 execute 的時候都會創建一個 FastThreadLocalThread 的線程實體。

接下來是創建 NioEventLoop,Netty 使用 for 循環來創建 nThreads 個 NioEventLoop,通過前面的分析,我們可能已經猜到,一個NioEventLoop對應一個線程實體,即 Netty 自己封裝的 FastThreadLocalThread。

來到 NioEventLoop 的構造方法

    NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
                 SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
                 EventLoopTaskQueueFactory taskQueueFactory, EventLoopTaskQueueFactory tailTaskQueueFactory) {
        super(parent, executor, false, newTaskQueue(taskQueueFactory), newTaskQueue(tailTaskQueueFactory),
                rejectedExecutionHandler);
       ......
        final SelectorTuple selectorTuple = openSelector();
        ......
    }

即創建了一個 Selector,Selector 是 NIO 編程裏最核心的概念,一個 Selector 可以將多個連接綁定在一起,負責監聽這些連接的讀寫事件,即多路複用。

繼續往上調用構造方法

    protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
                                        boolean addTaskWakesUp, Queue<Runnable> taskQueue,
                                        RejectedExecutionHandler rejectedHandler) {
        ......
        this.taskQueue = ObjectUtil.checkNotNull(taskQueue, "taskQueue");
        ......
    }

    

NioEventLoop 重寫了 taskQueue 的創建邏輯

    private static Queue<Runnable> newTaskQueue0(int maxPendingTasks) {
        // This event loop never calls takeTask()
        return maxPendingTasks == Integer.MAX_VALUE ? PlatformDependent.<Runnable>newMpscQueue()
                : PlatformDependent.<Runnable>newMpscQueue(maxPendingTasks);
    }

    private static Queue<Runnable> newTaskQueue(
            EventLoopTaskQueueFactory queueFactory) {
        if (queueFactory == null) {
            return newTaskQueue0(DEFAULT_MAX_PENDING_TASKS);
        }
        return queueFactory.newTaskQueue(DEFAULT_MAX_PENDING_TASKS);
    }

即創建一個 MPSC 隊列,

MPSC 隊列,Selector,NioEventLoop,這三者均爲一對一關係。

接下來是創建線程選擇器,

chooser = chooserFactory.newChooser(children);

這裏的選擇器是

    protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
        this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
    }

中的DefaultEventExecutorChooserFactory.INSTANCE,進入

    private static boolean isPowerOfTwo(int val) {
        return (val & -val) == val;
    }
    @Override
    public EventExecutorChooser newChooser(EventExecutor[] executors) {
        if (isPowerOfTwo(executors.length)) {
            return new PowerOfTwoEventExecutorChooser(executors);
        } else {
            return new GenericEventExecutorChooser(executors);
        }
    }

Netty 通過判斷 NioEventLoopGroup 中的 NioEventLoop 是否是2的冪來創建不同的線程選擇器,不管是哪一種選擇器,最終效果都是從第一個 NioEvenLoop 遍歷到最後一個NioEventLoop,再從第一個開始,如此循環。GenericEventExecutorChooser 通過簡單的累加取模來實現循環的邏輯,而 PowerOfTowEventExecutorChooser 是通過位運算實現的。

    private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
    ......
        @Override
        public EventExecutor next() {
            return executors[idx.getAndIncrement() & executors.length - 1];
        }
    ......
    }

    private static final class GenericEventExecutorChooser implements EventExecutorChooser {
    ......
        @Override
        public EventExecutor next() {
            return executors[(int) Math.abs(idx.getAndIncrement() % executors.length)];
        }
    ......
    }

最後總結一下,NioEventLoopGroup 的創建核心就三步

  1. 創建ThreadPerTaskExecutor;

  2. 創建NioEventLoop;

  3. 創建線程選擇器。

完整代碼見:hello-netty

本文所有圖例見:processon: Netty學習筆記

更多內容見:Netty專欄

參考資料

跟閃電俠學 Netty:Netty 即時聊天實戰與底層原理

深度解析Netty源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章