netty EventLoop 源碼分析(一)

netty 高併發物聯網交流羣 651219170

Netty 核心代碼分析

背景

都說是 netty 的 write() 在非 EventLoop 線程調用時線程安全的。但是沒看過源碼,總是覺得不安心。可能之前自己在寫 Reactor 的時候也總是在考慮怎麼解決 Reactor 線程 和普通線程 write() 怎麼解決競爭問題。所以抱着學習的態度學習了下 Netty 的 EventLoop 對各種事件的處理過程(包括了我一直糾結的 write()。

準備知識

java nio

這不是 java nio 的教程,所以我們只是描述下主要流程(不僅僅是 java 語言,其他的編程語言實現多路複用基本上步驟是一樣的)。

1.初始化一個多路複用器

selector = Selector.open() 

2.註冊感興趣的 channel 事件。

channel.register(selector, SelectionKey.OP_READ| SelectionKey.OP_WRITE);  

3.等待事件發生並處理

while(true){
                if(selector.select(TIMEOUT) == 0){
                    System.out.println("==");
                    continue;
                }
                //遍歷處理所有的事件
                Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                while(iter.hasNext()){
                    SelectionKey key = iter.next();
                    //準備好接受新鏈接
                    if(key.isAcceptable()){
                        handleAccept(key);
                    }
                    //處理可讀事件的請求
                    if(key.isReadable()){
                        handleRead(key);
                    }
                    //處理可寫事件的請求
                    if(key.isWritable() && key.isValid()){
                        handleWrite(key);
                    }
                    iter.remove();
                }
            }

EventLoop 的類圖

先看下 NioEventLoop 的類圖。通過這個類圖你只需要記住一點就是 NioEventLoop 是單線程,有序處理任務的線程池就好了。
這裏寫圖片描述

NioEventLoop 核心代碼

SingleThreadEventExecutor 是一個線程池,所以 excute() 會執行。
excute() 執行最終會調用子類 NioEventLoop 的 run() 方法。

NioEventLoop 核心部分 run()。

    //ioRate 是指此線程執行 io 操作佔時百分比,默認是 50
    protected void run() {
        for (;;) {
            try {
                //沒有任務,繼續 select() 等待滿足喚醒條件的操作(wakeup,事件發生),有任務執行 selectNow() 返回準備好事件的key的個數
            switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                    case SelectStrategy.CONTINUE:
                        continue;
                    case SelectStrategy.SELECT:
                        //繼續 select()
                        select(wakenUp.getAndSet(false));
                        if (wakenUp.get()) {
                            selector.wakeup();
                        }
                        // fall through
                    default:
                }

                cancelledKeys = 0;
                needsToSelectAgain = false;
                final int ioRatio = this.ioRatio;
                //ioRatio 爲 100 的時候就執行完 io 事件之後執行所有的 task
                if (ioRatio == 100) {
                    try {
                        processSelectedKeys();
                    } finally {
                        // Ensure we always run tasks.
                        runAllTasks();
                    }
                } else {
                    final long ioStartTime = System.nanoTime();
                    try {
                        //處理有事件發生的 key
                        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);
            }
        }
    }

核心部分爲 processSelectedKeys(); 處理有事件的所有的 key 的processSelectedKeys(); -> processSelectedKeysPlain()
循環處理所有有事件發生的 key

 Iterator<SelectionKey> i = selectedKeys.iterator();
        for (;;) {
            final SelectionKey k = i.next();
            //
            final Object a = k.attachment();
            i.remove();

            if (a instanceof AbstractNioChannel) {
                processSelectedKey(k, (AbstractNioChannel) a);
            } else {
                @SuppressWarnings("unchecked")
                NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
                processSelectedKey(k, task);
            }

            if (!i.hasNext()) {
                break;
            }
        }

接着看 processSelectedKey() 處理一個 key 的事件。
下面分別有對應的 accept write read 等事件的處理

        try {
            int readyOps = k.readyOps();
            // We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
            // the NIO JDK channel implementation may throw a NotYetConnectedException.
            if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
                // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
                // See https://github.com/netty/netty/issues/924
                int ops = k.interestOps();
                ops &= ~SelectionKey.OP_CONNECT;
                k.interestOps(ops);
                unsafe.finishConnect();
            }
            // Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
            if ((readyOps & SelectionKey.OP_WRITE) != 0) {
                // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
                ch.unsafe().forceFlush();
            }
            // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
            // to a spin loop
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
                unsafe.read();
            }
        } catch (CancelledKeyException ignored) {
            unsafe.close(unsafe.voidPromise());
        }

以上就已經基本清楚了, netty 對 io 的操作 nio Selector 編程的幾個步驟都是在 EventLoop 這個單線程中完成的。

好了,至於 netty 爲什麼在其他線程write() 是線程安全的。是怎麼實現的,在下節在討論。

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