Netty コアソースコード解析 (5) コアコンポーネント EventLoop ソースコード解析

シリーズ記事ディレクトリ

Nettyコアのソースコード解析(1)、Nettyのサーバーサイド起動処理のソースコード解析
Nettyのコアソースコード解析(2)、Nettyのサーバーサイドのリクエスト受信処理のソースコード解析
Nettyコアのソースコード解析(3) ビジネスリクエストの鍵実行 - ChannelPipeline、ChannelHandler、ChannelHandlerContext のソースコード解析
Netty コアのソースコード解析 (4) ハートビート検出のソースコード解析
Netty コアのソースコード解析 (5) コアコンポーネント EventLoop のソースコード解析

1. EventLoopのソースコード解析

以前に NioEventLoopGroup のソース コードを簡単に分析しました。今日は、EventLoop によって実行されるソース コードを分析します。

1. NioEventLoop のソースコード

まず、クラス継承図を分析してみましょう。
ここに画像の説明を挿入
(1) ScheduledExecutorService インターフェイスはスケジュールされたタスク インターフェイスを表し、EventLoop はスケジュールされたタスクを受け入れることができます。
(2) EventLoop インターフェイス: チャネルが登録されると、そのチャネルに対応するすべての IO 操作が処理されます。
(3) SingleThreadEventExecutor インターフェイスは、これがシングルスレッド スレッド プールであることを示します。
(4) EventLoop はシングルトン スレッド プールであり、ポートのリッスン、ポート イベントの処理、キュー イベントの処理という 3 つのことを常に実行するスレッドの無限ループが含まれています。各 EventLoop は複数の Channel にバインドでき、各 Channel は常に 1 つの EventLoop だけで処理できます。

2. SingleThreadEventExecutor、EventLoop の親インターフェイス

SingleThreadEventExecutor は、EventLoop によって使用されるソースである実行メソッドを含むシングルスレッド スレッド プールです。

// io.netty.util.concurrent.SingleThreadEventExecutor#execute
@Override
public void execute(Runnable task) {
    
    
    if (task == null) {
    
    
        throw new NullPointerException("task");
    }
	// 是否是当前线程
    boolean inEventLoop = inEventLoop();
    addTask(task);
    if (!inEventLoop) {
    
     // 如果该EventLoop的线程不是当前线程
        startThread(); // 开启线程
        if (isShutdown() && removeTask(task)) {
    
    
        	// 如果线程已经停止,并且删除任务失败,执行拒绝策略,默认是抛出异常RejectedExecutionException
            reject();
        }
    }
	// 如果addTaskWakesUp是false,并且任务不是NonWakeupRunnable类型的,尝试唤醒selector,这个时候,阻塞在selector的线程会立即返回
    if (!addTaskWakesUp && wakesUpForTask(task)) {
    
    
        wakeup(inEventLoop);
    }
}

(1) addTaskメソッド

// io.netty.util.concurrent.SingleThreadEventExecutor#addTask
protected void addTask(Runnable task) {
    
    
    if (task == null) {
    
    
        throw new NullPointerException("task");
    }
    if (!offerTask(task)) {
    
    
        reject(task);
    }
}
// io.netty.util.concurrent.SingleThreadEventExecutor#offerTask
final boolean offerTask(Runnable task) {
    
    
    if (isShutdown()) {
    
    
        reject();
    }
    return taskQueue.offer(task);
}

(2)startThread方法

まず、開始されているかどうかを確認し、EventLoop のスレッドが 1 つだけであることを確認します。開始されていない場合は、CAS を使用して状態を ST_STARTED に変更し、doStartThread を呼び出して開始します。

// 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);
            }
        }
    }
}
// io.netty.util.concurrent.SingleThreadEventExecutor#doStartThread
private void doStartThread() {
    
    
    assert thread == null;
    // executor就是创建EventLoopGroup时创建的ThreadPerTaskExecutor类,将runnable包装秤Netty的FastThreadLocalThread
    executor.execute(new Runnable() {
    
    
        @Override
        public void run() {
    
    
            thread = Thread.currentThread();
            // 判断中断状态
            if (interrupted) {
    
    
                thread.interrupt();
            }

            boolean success = false;
            // 设置最后一次的执行时间
            updateLastExecutionTime();
            try {
    
    
            	// this就是NioEventLoop,执行NioEventLoop的run方法,这个方法是个死循环,也是整个EventLoop的核心!
                SingleThreadEventExecutor.this.run();
                success = true;
            } catch (Throwable t) {
    
    
                logger.warn("Unexpected exception from an event executor: ", t);
            } finally {
    
    
            	// 使用CAS不断修改state状态,改成ST_SHUTTING_DOWN 
                for (;;) {
    
    
                    int oldState = state;
                    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 {
    
    
                    	// cheanup
                        cleanup();
                    } finally {
    
    
                    	// 修改状态ST_TERMINATED
                        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方法
                        terminationFuture.setSuccess(null);
                    }
                }
            }
        }
    });
}

3. NioEventLoop の run メソッド (コア!)

このメソッドは無限ループであり、NioEventLoop 全体の中核でもあります。

ソース コードから、run メソッドが合計 3 つのことを行うことがわかります:
(1) 関心のあるイベントを取得するために選択します。
(2) processSelectedKeys 処理イベント。
(3) runAllTask​​s はキュー内のタスクを実行します。

// 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
                    select(wakenUp.getAndSet(false));

                    // 'wakenUp.compareAndSet(false, true)' is always evaluated
                    // before calling 'selector.wakeup()' to reduce the wake-up
                    // overhead. (Selector.wakeup() is an expensive operation.)
                    //
                    // However, there is a race condition in this approach.
                    // The race condition is triggered when 'wakenUp' is set to
                    // true too early.
                    //
                    // 'wakenUp' is set to true too early if:
                    // 1) Selector is waken up between 'wakenUp.set(false)' and
                    //    'selector.select(...)'. (BAD)
                    // 2) Selector is waken up between 'selector.select(...)' and
                    //    'if (wakenUp.get()) { ... }'. (OK)
                    //
                    // In the first case, 'wakenUp' is set to true and the
                    // following 'selector.select(...)' will wake up immediately.
                    // Until 'wakenUp' is set to false again in the next round,
                    // 'wakenUp.compareAndSet(false, true)' will fail, and therefore
                    // any attempt to wake up the Selector will fail, too, causing
                    // the following 'selector.select(...)' call to block
                    // unnecessarily.
                    //
                    // To fix this problem, we wake up the selector again if wakenUp
                    // is true immediately after selector.select(...).
                    // It is inefficient in that it wakes up the selector for both
                    // the first case (BAD - wake-up required) and the second case
                    // (OK - no wake-up required).

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

            cancelledKeys = 0;
            needsToSelectAgain = false;
            final int ioRatio = this.ioRatio;
            if (ioRatio == 100) {
    
    
                try {
    
    
                	// 处理select keys
                    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);
        }
    }
}

(1)選択

一般的なロジックは次のとおりです: NIO のセレクターの select メソッドを呼び出し、デフォルトで 1 秒間ブロックします。スケジュールされたタスクがある場合は、ブロックするスケジュールされたタスクの残り時間に 0.5 秒を追加します。実行メソッドが実行されるとき、つまりタスクが追加されるとき、セレクターが長時間ブロックされないようにセレクターが起動されます。

// io.netty.channel.nio.NioEventLoop#select
private void select(boolean oldWakenUp) throws IOException {
    
    
    Selector selector = this.selector;
    try {
    
    
        int selectCnt = 0;
        long currentTimeNanos = System.nanoTime();
        long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
        for (;;) {
    
    
            long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
            if (timeoutMillis <= 0) {
    
    
                if (selectCnt == 0) {
    
    
                    selector.selectNow();
                    selectCnt = 1;
                }
                break;
            }

            // If a task was submitted when wakenUp value was true, the task didn't get a chance to call
            // Selector#wakeup. So we need to check task queue again before executing select operation.
            // If we don't, the task might be pended until select operation was timed out.
            // It might be pended until idle timeout if IdleStateHandler existed in pipeline.
            if (hasTasks() && wakenUp.compareAndSet(false, true)) {
    
    
                selector.selectNow();
                selectCnt = 1;
                break;
            }
			// 阻塞给定时间,默认一秒
            int selectedKeys = selector.select(timeoutMillis);
            selectCnt ++;
			// 如果有返回值||select被用户唤醒||任务队列有任务||有定时任务即将被执行,则跳出循环
            if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
    
    
                // - Selected something,
                // - waken up by user, or
                // - the task queue has a pending task.
                // - a scheduled task is ready for processing
                break;
            }
            if (Thread.interrupted()) {
    
    
                // Thread was interrupted so reset selected keys and break so we not run into a busy loop.
                // As this is most likely a bug in the handler of the user or it's client library we will
                // also log it.
                //
                // See https://github.com/netty/netty/issues/2426
                if (logger.isDebugEnabled()) {
    
    
                    logger.debug("Selector.select() returned prematurely because " +
                            "Thread.currentThread().interrupt() was called. Use " +
                            "NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");
                }
                selectCnt = 1;
                break;
            }

            long time = System.nanoTime();
            if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
    
    
                // timeoutMillis elapsed without anything selected.
                selectCnt = 1;
            } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
                    selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
    
    
                // The selector returned prematurely many times in a row.
                // Rebuild the selector to work around the problem.
                logger.warn(
                        "Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.",
                        selectCnt, selector);

                rebuildSelector();
                selector = this.selector;

                // Select again to populate selectedKeys.
                selector.selectNow();
                selectCnt = 1;
                break;
            }

            currentTimeNanos = time;
        }

        if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {
    
    
            if (logger.isDebugEnabled()) {
    
    
                logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
                        selectCnt - 1, selector);
            }
        }
    } catch (CancelledKeyException e) {
    
    
        if (logger.isDebugEnabled()) {
    
    
            logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
                    selector, e);
        }
        // Harmless exception - log anyway
    }
}

4. まとめ

ececute メソッドを実行するたびに、タスクがキューに追加されます。スレッドが初めて追加されると、run メソッドが実行されます。run メソッドは、EventLoop、Loop Loop、ノンストップ ループの名前のように、EventLoop 全体のコアです。Loop は何をしますか? 3つのこと。

  • セレクターの select メソッドを呼び出します。デフォルトは 1 秒です。スケジュールされたタスクがある場合は、スケジュールされたタスクの残り時間に 0.5 秒を追加することでブロックされます。実行メソッドが実行されるとき、つまりタスクが追加されるとき、セレクターが長時間ブロックされないようにセレクターが起動されます。
  • セレクターが返されると、 processSelectedKeys メソッドが呼び出されて、selectKey が処理されます。
  • processSelectedKeys メソッドが実行されると、ioRatio の比率に従って runAlITasks メソッドが実行されます。デフォルトでは、IO タスク時間と非 IO タスク時間は同じですが、アプリケーションの特性に応じて調整することもできます。たとえば、非 IO タスクが多数ある場合は、非 IO タスクをより長く実行できるように ioRatio を下げる必要があります。キューに多くのタスクが蓄積されないようにします。

この時点で、下図の赤丸で囲ったソースコードの解析が完了しました。
ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/A_art_xiang/article/details/130363234