netty源碼分析(9)-NioEventLoop執(zhí)行過(guò)程之select()

上一節(jié)我們研究了NioEventLoop的執(zhí)行過(guò)程悼院,但是select(wakenUp.getAndSet(false));processSelectedKeys();沒(méi)有分析硼身。

這一節(jié)研究select(),改方法干的事情其實(shí)是輪詢檢查IO事件啊送。有三個(gè)階段

  1. 第一個(gè)階段据途,計(jì)算超時(shí)時(shí)間并判斷是否超時(shí),超時(shí)則進(jìn)行一次非阻塞式的select郎任,中斷輪詢檢測(cè);沒(méi)有超時(shí)备籽,同樣也會(huì)判斷taskQueue和tailQueue中是否有有任務(wù)舶治,有任務(wù)也進(jìn)行同樣的操作
  2. 第二個(gè)階段,未超時(shí)车猬,且任務(wù)隊(duì)列為空霉猛,進(jìn)行阻塞式的select,直到檢測(cè)到一個(gè)已注冊(cè)的IO事件發(fā)生,阻塞時(shí)間默認(rèn)為1秒鐘珠闰。
  3. 第三個(gè)階段惜浅,該階段主要的作用就是防止空輪詢bug,導(dǎo)致cpu暫用率過(guò)高導(dǎo)致宕機(jī)的情況伏嗜。

    private void select(boolean oldWakenUp) throws IOException {
        Selector selector = this.selector;
        try {
            int selectCnt = 0;
            long currentTimeNanos = System.nanoTime();
            //當(dāng)前時(shí)間 + 截止時(shí)間 (NioEventLoop底層維持了一個(gè)定時(shí)任務(wù)隊(duì)列坛悉,按照任務(wù)的截止時(shí)間正序排序)
            //delayNanos(currentTimeNanos) : 計(jì)算當(dāng)前定時(shí)任務(wù)隊(duì)列第一個(gè)任務(wù)的截止時(shí)間
            long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
            //輪詢
            for (;;) {

                //計(jì)算超時(shí)時(shí)間
                long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
                //判斷是否超時(shí)
                if (timeoutMillis <= 0) {
                    //超時(shí)伐厌,判斷有沒(méi)有select
                    if (selectCnt == 0) {
                        //沒(méi)有select,進(jìn)行非阻塞的select,異步檢測(cè)IO事件裸影。不阻塞
                        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.

                //未超時(shí)
                //判斷當(dāng)前任務(wù)隊(duì)列(taskQueue)和異步任務(wù)隊(duì)列(tailQueue)是否有任務(wù)
                if (hasTasks() && wakenUp.compareAndSet(false, true)) {
                    //有任務(wù)挣轨,進(jìn)行一次非阻塞式的select
                    selector.selectNow();
                    selectCnt = 1;
                    break;
                }

                //第二個(gè)階段
                //未超時(shí),且任務(wù)隊(duì)列為空的話轩猩,進(jìn)行阻塞式的select,直到檢測(cè)到一個(gè)已注冊(cè)的IO事件發(fā)生卷扮,timoutMillis是阻塞最大時(shí)間,默認(rèn)1000,
                int selectedKeys = selector.select(timeoutMillis);
                selectCnt ++;

                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;
                }

                //第三個(gè)階段
                //防止jdk空輪詢的bug

                long time = System.nanoTime();
                //判斷執(zhí)行了一次阻塞式select后均践,當(dāng)前時(shí)間和開(kāi)始時(shí)間是否大于超時(shí)時(shí)間画饥。(大于是很正常的,小于的話浊猾,說(shuō)明沒(méi)有執(zhí)行發(fā)生了空輪詢)
                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 code exists in an extra method to ensure the method is not too big to inline as this
                    // branch is not very likely to get hit very frequently.
                    // SELECTOR_AUTO_REBUILD_THRESHOLD的值為512
                    //selectCut >= 512 也就是空輪詢測(cè)試大于等于512次的話抖甘,空輪詢處理發(fā)生,調(diào)用改方法避免下一次發(fā)生
                    selector = selectRebuildSelector(selectCnt);
                    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
        }
    }
  • 重點(diǎn)說(shuō)一下防止空輪詢bug葫慎,在第三階段衔彻,代碼time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos

time:當(dāng)前時(shí)間
timeoutMillis:阻塞式select超時(shí)時(shí)間
currentTimeNanos:本方法開(kāi)始時(shí)間

因此滿足該條件就是,經(jīng)過(guò)了阻塞式的select檢測(cè)IO事件偷办,但是并沒(méi)有任何事件發(fā)生艰额,盡力了。這時(shí)候select的計(jì)數(shù)加一椒涯,累計(jì)到閾值SELECTOR_AUTO_REBUILD_THRESHOLD的時(shí)候則滿足了空輪詢的條件柄沮,該閾值在本類靜態(tài)代碼塊中初始化為512,經(jīng)過(guò)512次什么事情都沒(méi)有做,表表示滿足空輪詢的條件废岂。

//selectCut >= 512 也就是空輪詢測(cè)試大于等于512次的話祖搓,空輪詢處理發(fā)生,調(diào)用改方法避免下一次發(fā)生
selector = selectRebuildSelector(selectCnt);

    private Selector selectRebuildSelector(int selectCnt) throws IOException {
        // 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);
        //充值Selector
        rebuildSelector();
        Selector selector = this.selector;
        //非阻塞select 檢測(cè)IO事件
        // Select again to populate selectedKeys.
        selector.selectNow();
        return selector;
    }

    public void rebuildSelector() {
        if (!inEventLoop()) {
            execute(new Runnable() {
                @Override
                public void run() {
                    rebuildSelector0();
                }
            });
            return;
        }
        rebuildSelector0();
    }

通過(guò)分析源碼湖苞,發(fā)現(xiàn)空輪詢bug的解決策略就是重置selector拯欧,重新打開(kāi)一個(gè)selector,依照舊的selector重新注冊(cè)所有的IO事件到原有通道上财骨,生成新的selectionKey镐作,并刷新該值,同時(shí)刷新的還有和selector相關(guān)的屬性值,最后把舊的給關(guān)閉掉隆箩。這樣不會(huì)無(wú)限死循環(huán)该贾,僅僅會(huì)替換掉原有的屬性值而已,做了一些動(dòng)作捌臊。

    private void rebuildSelector0() {
        final Selector oldSelector = selector;
        final SelectorTuple newSelectorTuple;

        if (oldSelector == null) {
            return;
        }

        try {
            //重新創(chuàng)建一個(gè)selector
            newSelectorTuple = openSelector();
        } catch (Exception e) {
            logger.warn("Failed to create a new Selector.", e);
            return;
        }

        // Register all channels to the new Selector.
        int nChannels = 0;
        //輪詢舊操作的所有的key和attachment 
        //attachment 其實(shí)就是netty包裝了的channel
        for (SelectionKey key: oldSelector.keys()) {
            //獲取channel
            Object a = key.attachment();
            try {
                if (!key.isValid() || key.channel().keyFor(newSelectorTuple.unwrappedSelector) != null) {
                    continue;
                }
                //注冊(cè)key注冊(cè)的事件
                int interestOps = key.interestOps();
                //取消之前key的事件
                key.cancel();
                //將事件和channel注冊(cè)到新創(chuàng)建的selector上
                SelectionKey newKey = key.channel().register(newSelectorTuple.unwrappedSelector, interestOps, a);
                if (a instanceof AbstractNioChannel) {
                    // Update SelectionKey
                    //替換新的selectionKey
                    ((AbstractNioChannel) a).selectionKey = newKey;
                }
                nChannels ++;
            } catch (Exception e) {
                logger.warn("Failed to re-register a Channel to the new Selector.", e);
                if (a instanceof AbstractNioChannel) {
                    AbstractNioChannel ch = (AbstractNioChannel) a;
                    ch.unsafe().close(ch.unsafe().voidPromise());
                } else {
                    @SuppressWarnings("unchecked")
                    NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
                    invokeChannelUnregistered(task, key, e);
                }
            }
        }
        //重置新的selector
        selector = newSelectorTuple.selector;
        unwrappedSelector = newSelectorTuple.unwrappedSelector;

        try {
            // time to close the old selector as everything else is registered to the new one
            //關(guān)閉舊的selector
            oldSelector.close();
        } catch (Throwable t) {
            if (logger.isWarnEnabled()) {
                logger.warn("Failed to close the old Selector.", t);
            }
        }

        if (logger.isInfoEnabled()) {
            logger.info("Migrated " + nChannels + " channel(s) to the new Selector.");
        }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末杨蛋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌六荒,老刑警劉巖护姆,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矾端,死亡現(xiàn)場(chǎng)離奇詭異掏击,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)秩铆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門砚亭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人殴玛,你說(shuō)我怎么就攤上這事捅膘。” “怎么了滚粟?”我有些...
    開(kāi)封第一講書人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵寻仗,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我凡壤,道長(zhǎng)署尤,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任亚侠,我火速辦了婚禮曹体,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘硝烂。我一直安慰自己箕别,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布滞谢。 她就那樣靜靜地躺著串稀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪狮杨。 梳的紋絲不亂的頭發(fā)上厨诸,一...
    開(kāi)封第一講書人閱讀 51,231評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音禾酱,去河邊找鬼微酬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛颤陶,可吹牛的內(nèi)容都是我干的颗管。 我是一名探鬼主播,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼滓走,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼垦江!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起搅方,我...
    開(kāi)封第一講書人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤比吭,失蹤者是張志新(化名)和其女友劉穎绽族,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體衩藤,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吧慢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赏表。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片检诗。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖瓢剿,靈堂內(nèi)的尸體忽然破棺而出逢慌,到底是詐尸還是另有隱情,我是刑警寧澤间狂,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布攻泼,位于F島的核電站,受9級(jí)特大地震影響鉴象,放射性物質(zhì)發(fā)生泄漏忙菠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一炼列、第九天 我趴在偏房一處隱蔽的房頂上張望只搁。 院中可真熱鬧,春花似錦俭尖、人聲如沸氢惋。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至已亥,卻和暖如春熊赖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背虑椎。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工震鹉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人捆姜。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓传趾,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親泥技。 傳聞我的和親對(duì)象是個(gè)殘疾皇子浆兰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容