兩篇文章看懂EventLoopGroup,EventLoop的設計和運行機制(二)

前言

上一篇更多的是說結論,那結論是怎麼來的呢?也是一步一步學習出來的,我在學習過程中,使用了Xmind,用來做思維導圖還是比較好的,層次結構以及每個方法都可以很好的記錄。本文重點分享一下下面這個類圖以及每個類中的實現細節:
在這裏插入圖片描述

EventExecutorGroup和EventExecutor

這兩個接口是上述類圖中的基礎,也是EventLoop的基礎,特別是EventExecutorGroup,是netty自定義的第一個接口,先學習好它們,也就能學習好EventLoop了。
還是按照我的思路,先刪減,後增補。

EventExecutorGroup

先看一下拆分的類圖:
在這裏插入圖片描述
它本身繼承了4個重要接口,以及它們各自的功能:

  • Iterable:迭代器接口,返回一個迭代器。說明已經具備了Group的管理或者容器功能。
  • Executor:具備了提交任務的能力。
  • ExecutorService:具備了線程池的能力。
  • ScheduledExecutorService:具備了執行調度任務的能力。

這4種能力不多說,都是JDK本身提供的,我們傑西萊看一下,它本身定義了哪些方法呢?如下圖:
在這裏插入圖片描述
雖然有很多方法,但大多數都是上面接口的,重新寫了一遍而已,我們對它新加的一些方法做一些分析。

  • 它優化了JDK提供的關於中斷的方法:標記過時(shutdown,shutdownNow),並且新加了幾個方法
    • boolean isShuttingDown();
    • Future<?> shutdownGracefully();
    • Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit);
  • 增加了一箇中斷狀態監聽的方法
    • Future<?> terminationFuture()。注意這個Future是netty的.
    • 可以在這個future上面增加一些監聽器(addListener),然後當中斷的時候就會觸發。
  • 增加了next方法。返回的是由它管理的EventExecutor。這個組的味道已經出現了。這個也是後面實現輪詢內部成員的一個關鍵方法。
    這個接口的功能就是:線程池,管理一組EventExecutor,調度任務,中斷監聽。

我們單從Group的功能來看的話,這個接口下面就再也沒有其它接口了,都是一些抽象類了,下面是一個比較簡單的類圖:
在這裏插入圖片描述

AbstractEventExecutorGroup

這個類沒有任何的實現,僅僅只是把所有的提交任務的方法做了一下默認實現:就是調用一些next()方法輪詢出來一個Executor然後去執行,比如:

  @Override
    public void execute(Runnable command) {
        next().execute(command);
    }

其它的所有方法都沒有實現,也確實挺抽象的。
不過這個抽象類的作用還是非常大的,之前在分析組和成員關係的時候,組也具備了一些成員關係的功能,但是它的執行就是通過成員去完成的,而且這些功能本身也確實不需要組去關心的。而netty就是把這些功能放在了這個類裏面,也放的特別合適,它沒有實現任何有關於的組的管理功能,實際上就是交給子類去完成了,而子類在實現管理功能的時候,也就不需要關心這些功能了(提交任務),父類都已經實現了。

MultithreadEventExecutorGroup

聽名字就可以聽出來一些味道了:多線程事件執行器組。
3個關鍵詞:多線程,事件執行器,組。
這個裏面纔是真正實現了組的管理功能,看一下它內部所有的屬性和方法:

在這裏插入圖片描述
先看一下內部屬性吧:

  • EventExecutor[] children:固定大小的EventExecutor數組,構造器裏面會根據大小全部初始化成員。
  • EventExecutorChooser chooser:成員輪詢器。雖然有兩種實現,但其實都是按照(0,1,2,3,…,n-1)的順序來的。只不過當大小是2的冪的時候,採用了一下位的和(&)運算,會稍微快一些。這個就是next方法的實現。
  • terminationFuture:中斷的一個Future,可以用來監聽中斷事件。
  • readonlyChildren:用一個不可寫的集合來做迭代器用。
  • terminatedChildren:原子int,用來標記當前有多少個成員已經被中斷了,當所有的都被中斷的時候,就會觸發中斷事件。

這個類比較核心的就是它的構造器了,至於其它的關於中斷的方法,基本上也都是循環中斷所有的成員,不是特別複雜。當然還有一個創建成員的抽象方法,不過也是和構造器相呼應的。

關於它的構造器和一個抽象方法,我覺得也是比較有趣的點。

  • 它的所有的構造器都是protected的,說明不是對外開放的,由子類去調用的。

  • 最有趣的在於構造器的最後一個參數居然是:Object… args。在構造器裏面,這種寫法確實還是比較少見的。

  • 這個類僅有一個抽象方法:

    protected abstract EventExecutor newChild(Executor executor, 	Object... args) throws Exception
    

    就是在初始化成員的時候需要調用的方法,而且它的最後一個參數也是 Object… args。其實構造器的對象數組的入參和這個入參是相呼應的,就是同一個。

  • 這個構造器當時在定義的時候,開發人員估計費了不少的心思啊,哈哈。

  • 那爲什麼要這樣寫呢?那肯定是爲了複用,複用這些組的管理功能。它所管理的Executor到底需要哪些參數,以及如何來創建。它不關心,它也沒法關心;所以留到了子類,同時它希望對於具體的子類而已,那個對象數組不能暴露給框架的使用人員,希望他們具體化,不能這麼抽象的使用,所以構造器全部protected。

MultithreadEventExecutorGroup到這邊就結束了,它更多的是完成了成員的初始化以及中斷的相關內容。但是成員EventExecutor到底什麼?且看下面的分析。

DefaultEventExecutorGroup

這個類非常簡單,提供了3個構造器,實現了newChild方法,並且明確了成員就是DefaultEventExecutor。
當我學習到這邊的時候,就基本非常明確了,它就是一個線程池,我第一個想到的就是和ThreadPoolExecutor進行比較,之前的文章以及比較過了。但是在比較之前,想了一下,我好像還並不知道任務是怎麼執行的,也就是EventExecutor的具體實現了。

EventExecutor

這個接口才是真正的執行任務的接口:
在這裏插入圖片描述
它繼承了EventExecutorGroup,說明它也擁有上面說的所有功能。
重點關注一下,它新定義的方法吧:
在這裏插入圖片描述
它基本上新定義了3類接口:

  • parent():返回父節點。也反映了它被EventExecutorGroup管理的特性。
  • inEventLoop():判斷當前線程是不是當前EventExecutor所關聯的事件循環的線程。非常重要的方法,好多地方都會用到。因爲在後續的提交任務的時候,有可能是事件循環的線程(這個就是提交的任務在執行過程當中又提交了新的任務),有可能是其它線程。然後可能需要做一些線程安全方面的工作。
  • 創建Promise,或者Future的方法。這個我沒有用過,但是也瞭解了一些,簡單說一下,可能不太對。這兩種對象都代表的是異步執行的結果,前者相對於後者具備了寫的功能,後者只可讀。但是他們都具備結束然後事件通知的能力,那麼誰來通知呢?就是當前EventExector所關聯的線程去通知。也就是通過它創建的,都會用它的關聯線程去執行通知任務。但是具體的應用場景還不太清楚。

以後就是它比Group多出來的方法,一方面體現了它是成員角色,另一方面它也可以做額外的事情。從下面開始,也就沒有接口定義了,都是具體的類了。

然後再看一下DefaultEventExecutor的整體類圖:
在這裏插入圖片描述

AbstractEventExecutor

這個類一方面實現了EventExecutor,另一方也繼承了JDK提供的AbstractExecutorService。後面這個類更多的是提供提交一些任務的默認實現,也沒有做具體的業務實現。
AbstractEventExecutor它本身也沒有做太多事情,只是把接口和抽象類整合在了一起。稍微梳理一下吧:
在這裏插入圖片描述

  • parent:定義了父對象。

  • selfCollection:返回一個屬於自己的迭代器。

  • promise,future的方法都做了實現,如下:

    @Override
    public <V> Promise<V> newPromise() {
        return new DefaultPromise<V>(this);
    }
    
    @Override
    public <V> ProgressivePromise<V> newProgressivePromise() {
        return new DefaultProgressivePromise<V>(this);
    }
    
    @Override
    public <V> Future<V> newSucceededFuture(V result) {
        return new SucceededFuture<V>(this, result);
    }
    
    @Override
    public <V> Future<V> newFailedFuture(Throwable cause) {
        return new FailedFuture<V>(this, cause);
    }
    
  • 關於定時任務的,都直接拋了異常,不支持。

  • 提供了一個EventExecutorGroup的一個構造器。

沒有做太多的事情,做一些默認實現。重點看一下它的子類。

AbstractScheduledEventExecutor

看這個名字基本也能猜出來,它實現了調度任務:
在這裏插入圖片描述

  • 內部有一個優先級隊列,最早執行的會放在隊首,每次提交任務的時候會進行調整。它這個不是線程安全的隊列。因此它在添加調度任務的時候,如果不是事件循環線程的話,會提交一個新的普通任務取提交任務,保證線程安全:

    <V> ScheduledFuture<V> schedule(final ScheduledFutureTask<V> task) {
        if (inEventLoop()) {
            scheduledTaskQueue().add(task);
        } else {
            execute(new Runnable() {
                @Override
                public void run() {
                    scheduledTaskQueue().add(task);
                }
            });
        }
    
        return task;
    }
    
  • 所有被提交的調度任務都會被封裝成ScheduledFutureTask,這個對象裏面有幾個關鍵點:

    • 當這個類被內存加載的時候,會生成當前時間的一個納秒時間戳。以後所有的時間計算都會以它作爲開始時間的標準,比如獲取當前納米戳=獲取當前納秒時間戳的然後減去開始時間就是當前時間:

      private static final long START_TIME = System.nanoTime();
      
      static long nanoTime() {
          return System.nanoTime() - START_TIME;
      }
      
    • deadlineNanos:這個任務應該被執行的納秒時間戳。都是通過比較這個屬性值來判斷當前任務是不是到時間去執行了。

    • periodNanos:調度任務的執行週期。爲0的話,代表只執行一次。否則,就認爲是週期不斷執行的任務,間隔就是periodNanos。

    • 關鍵在於它的run方法,解決了如何執行週期任務。當一個週期任務執行完了,它會把periodNanos加到deadlineNanos上面取,作爲新的執行時間,然後重新加入隊列。然後就可以再執行了:

      @Override
          public void run() {
              assert executor().inEventLoop();
              try {
                  if (periodNanos == 0) {
                      if (setUncancellableInternal()) {
                          V result = task.call();
                          setSuccessInternal(result);
                      }
                  } else {
                      // check if is done as it may was cancelled
                      if (!isCancelled()) {
                          task.call();
                          if (!executor().isShutdown()) {
                              long p = periodNanos;
                              if (p > 0) {
                                  deadlineNanos += p;
                              } else {
                                  deadlineNanos = nanoTime() - p;
                              }
                              if (!isCancelled()) {
                                  // scheduledTaskQueue can never be null as we lazy init it before submit the task!
                                  Queue<ScheduledFutureTask<?>> scheduledTaskQueue =
                                          ((AbstractScheduledEventExecutor) executor()).scheduledTaskQueue;
                                  assert scheduledTaskQueue != null;
                                  scheduledTaskQueue.add(this);
                              }
                          }
                      }
                  }
              } catch (Throwable cause) {
                  setFailureInternal(cause);
              }
          }
      
  • 同時它也提供了一些取出到了執行時間任務的一些方法,供子類使用。

雖然它實現了提交定時任務,以及如何來完成周期任務的執行,但是要怎麼觸發並且執行它們呢?它沒說。

SingleThreadEventExecutor

單線程事件執行器,意思以及很明確了,用一個線程去執行所有的事件。
這個類的屬性和方法有點多,生成的圖不好看,IDEA也不能刪除其中幾個。那就沒有圖了,調幾個重要的:

private final Queue<Runnable> taskQueue;
private volatile Thread thread;
private final boolean addTaskWakesUp;
private final int maxPendingTasks;

簡單說一下這幾個屬性吧:

  • taskQueue:任務隊列,所有提交的任務會執行扔到這個隊列裏面。注意它的定義並不是阻塞隊列。但是它創建的時候,提供的默認實現就是阻塞隊列,並且它提供的一個內部方法takeTask(),取任務的時候還強制要求必須是阻塞隊列,否則就拋異常。剛開始還挺鬱悶的,想不明白。最後發現創建隊列的方法被NIOEventLoop給重寫了,它那邊提供的是一個無鎖高性能隊列MPSC(多生茶這,單消費者,這就是它的消費模型)隊列,這個並不是阻塞隊列。這或許解釋得通?
  • thread:事件循環的線程。會在線程啓動以後賦值這個變量,它是用Executor啓動的,而不是直接一個線程。
  • addTaskWakesUp:如果爲true,意味着:當且僅當執行addTask(Runnable)方法的時候,會喚醒執行器的線程。我對這個變量沒有搞太明白,它這邊提供的構造器默認是true,而EventLoop那邊變成了false,而且我很明確它爲false的時候提交一個新任務進來纔可以喚醒正在阻塞輪詢的線程。它說明中的addTask方法,也是僅僅是提交任務的時候會去觸發:
    @Override
    public void execute(Runnable task) {
        if (task == null) {
            throw new NullPointerException("task");
        }
    
        boolean inEventLoop = inEventLoop();
        if (inEventLoop) {
            addTask(task);
        } else {
            startThread();
            addTask(task);
            if (isShutdown() && removeTask(task)) {
                reject();
            }
        }
    
        if (!addTaskWakesUp && wakesUpForTask(task)) {
            wakeup(inEventLoop);
        }
    }
    
    重點可以看一下後面兩行,它必須爲true,纔會去執行wakeUp方法。所以對於它的描述,實現是看不明白。不解釋了,有明白的可以分享一下。
  • maxPendingTasks:隊列的最大容量。

從功能上來講,這個類主要做了這麼幾件事情:

  • 任務隊的創建。
  • 循環線程的啓動,當提交第一個任務的時候就會啓動線程:
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);
                }
            }
        }
    }
private void doStartThread() {
        assert thread == null;
        executor.execute(new Runnable() {
            @Override
            public void run() {
                thread = Thread.currentThread();
                if (interrupted) {
                    thread.interrupt();
                }

                boolean success = false;
                updateLastExecutionTime();
                try {
                    SingleThreadEventExecutor.this.run();
                    success = true;
                } catch (Throwable t) {
                    logger.warn("Unexpected exception from an event executor: ", t);
                }
               //其它的省略一下
           }
        });
    }

啓動以後,它重點就做了一個事情,執行run方法。這是個抽象方法。也就是真正的循環體這邊也沒有實現。

  • 任務的提交。實現了execute方法,這也是非常關鍵的方法。
  • 提供了一些從隊列裏面取任務的方法,供子類用,畢竟它定義隊列成private了,比如:
    • Runnable pollTask()
    • Runnable pollTaskFrom(Queue taskQueue)
    • Runnable takeTask()
    • boolean fetchFromScheduledTaskQueue()
    • Runnable peekTask()
  • 還提供了一些執行任務的方法,比如:
    • boolean runAllTasks()
    • runAllTasksFrom(Queue taskQueue)
    • runAllTasks(long timeoutNanos)

它的職責就這些,該做的都做了,但是我要怎麼循環取任務呀,好像也沒說。這就剩下最關鍵的一個方法run了。它也是EventLoop最大的區別了。

DefaultEventExecutor

默認的事件執行器。

它實現了run方法,無限循環:從隊列裏面取出每一個任務,然後去執行:

    @Override
    protected void run() {
        for (;;) {
            Runnable task = takeTask();
            if (task != null) {
                task.run();
                updateLastExecutionTime();
            }

            if (confirmShutdown()) {
                break;
            }
        }
    }

然後就完了,這個類其實也挺簡單的。
到此,成員Executor就結束了。不知道了有沒有講清楚,可能也有一些細節都忽略掉了,不過整體它的運行機制應該都講到了。
話說寫到這邊的話,它的篇幅已經超過了第一篇了,沒有想到寫了這麼多。主角EventLoop都還沒有上場呢。。。還是那句話,這個整明白了,EventLoop也就比較容易了,事實也是如此。

EventExecutorGroup與EventExecutor

上面一直在說這兩個,說完了,我再放一張圖吧,不解釋了,上篇文章裏面有:
在這裏插入圖片描述
我是按照類圖的遞進關係來學習和講解的,每個類的職責都會說到,雖然我內心是比較明白的,但是總感覺好像缺了一張有關於每個類的職責的循序漸進圖,可以很明顯的表現出它們之間的關係,我也沒有畫。
不過話說回來,對於我們學習者而言,結論重要還是它的整個學習過程重要呢?我的學習初衷就是想了解它的運行機制,僅此而已。而關於類的職責劃分,或許這是當時框架的開發者去思考的,但確實我們學習的人也能站在它們的角度去思考,更方面我們的理解,但是,一定要把握好度,差不多就行了。因此,上面那個圖,有了更好,沒有也行。

接下來看一下重頭戲吧。

EventLoopGroup和EventLoop

從我的學習過程看,當學習完上面兩個,這邊的內容已經是特別少了,主要增加了通道(Channle)和多路複用器(Selector)的內容。
放一張NIOEventLoopGroup和NIOEventLoop的類圖吧:
在這裏插入圖片描述

EventLoopGroup

直接看一下EventLoopGroup到NIOEventLoopGroup的類圖吧:
在這裏插入圖片描述
EventLoopGroup,它繼承了EventExecutorGroup,所以我們上面講的功能它都具備。
看一下它的接口定義:
在這裏插入圖片描述
4個方法:

  • next():覆蓋了父類的方法,把返回值換成了EventLoop。
  • register(Channel channel):註冊通道。
  • register(ChannelPromise promise):註冊通道。
  • register(Channel channel, ChannelPromise promise):廢棄的註冊通道的方法。

注意它的第一個註冊的實現還是調用了第二個方法,因此說白了,它就增加了一個方法:註冊通道。沒了。
關於這個方法多說兩句哈,使用NIO編程的時候,我們需要使用創建一個SocketChannel,然後再把它註冊在多路複用器上面。而netty也是通過這個方法把通道綁定在了EventLoopGroup所管理的其中一個EventLoop中關聯的多路複用器上面。和java的nio結合了起來。但其實EventLoop接口本身並沒有定義有關多路複用器相關的操作;因此在實現這個方法的時候,AbstractNioChannel所依賴的就是很明確的NIOEventLoop了,屬性定義雖然是接口,但是用的時候進行強行類型轉換了。這個其實還是挺鬱悶的。
EventLoop上面爲什麼不定義和多路複用器相關的方法。這個可能目前能想來一點點,因爲能看見的它的實現有好幾種(還沒有研究它們有什麼用),但只有NIOEventLoop使用到了多路複用器。
突然提到了EventLoop,劇透一下,它繼承了EventLoopGroup,然而一個新的方法都沒有定義。
Channel中的屬性爲什麼沒有直接定義成NIOEventLoop呢?在子類AbstractNioChannel中直接強轉了,這邊代碼看得少,再看看或許會有答案的。

不過就算想不明白,也不用去糾結這個。因爲完全不影響整體的一個分析。

MultithreadEventLoopGroup

這個類做的事情更少,就做了一個事情:把MultithreadEventExecutorGroup(上面講過了)和EventLoopGroup結合在了一起。
那些註冊通道之類的也都是調用next()方法讓EventLoop去做了。
額外還有個事情,它把這個裏面的線程的優先級設爲了10(最高),MultithreadEventExecutorGroup這個裏面只有5。也是爲了優先處理IO請求吧。

NioEventLoopGroup

實現了newChild方法,創建成員的時候,返回的就是NioEventLoop。

    @Override
    protected EventLoop newChild(Executor executor, Object... args) throws Exception {
        return new NioEventLoop(this, executor, (SelectorProvider) args[0],
            ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
    }

增加了幾個方法:

  • setIoRatio:設置所有的成員的IO比例
  • rebuildSelectors:重置所有成員的多路複用器
    這兩個方法沒有定義在接口上面,可能是因爲最後的實現類裏面纔有了這兩個方法。

然後就沒了~~~突然覺得好簡單啊,確實就這麼簡單。

EventLoop

來個NIOEventLoop的簡單類圖瞅一下:
在這裏插入圖片描述
它繼承了EventLoopGroup和EventExecutor(上面講過了),但是如我上面所說的,它啥都沒有定義,看看:

public interface EventLoop extends OrderedEventExecutor, EventLoopGroup {
    @Override
    EventLoopGroup parent();
}

就沒了,所以也沒有可講的了。

SingleThreadEventLoop

它繼承了SingleThreadEventExecutor(上面講過了),實現了EventLoop,也就是把兩者結合在一起了。
它實現了註冊通道的方法:

    @Override
    public ChannelFuture register(Channel channel) {
        return register(new DefaultChannelPromise(channel, this));
    }

    @Override
    public ChannelFuture register(final ChannelPromise promise) {
        ObjectUtil.checkNotNull(promise, "promise");
        promise.channel().unsafe().register(this, promise);
        return promise;
    }

看這個代碼,真的是什麼都看不出來。
寫過NIO代碼的應該就會理解,具體的註冊邏輯交給了通道本身,這個和jdk的實現策略是一致的。它一行代碼內部肯定會執行NIO的原生代碼的。

增加了一個看着不知道有什麼用的功能,增加了tailQueue,也可以往裏面加一些任務,在每次事件輪詢之後執行。不知道有啥用。

好像也沒有其它重點內容了。越來越簡單了。

NIOEventLoop

這個類是真的一點都不簡單,畢竟加入了多路複用器,不過我不詳細講了,因爲網上講得非常多,也非常細,我還是強調一些關鍵點吧。
(我直接抄的第一篇裏面的)

  • EventLoop裏面關鍵屬性有兩個,多路複用器Selector和任務隊列。可以把通道(Channel)註冊在多路複用器上面,可以不斷輪詢其中的事件然後執行。任務隊列存儲提交的task。
  • EventLoop處理的事件(叫任務也行,事件更加貼切吧)整體上有兩種:
    • IO事件。當一個EventLoop所關聯的多路複用器上面註冊的通道發生“連接、接收(Acceptor)、讀、寫”事件的時候,就相當於觸發了IO事件。一般也就兩種場景:作爲server端的時候,監聽一個端口,別人來訪問你的端口,就會先觸發接收事件,然後讀取,寫入事件。作爲client端的時候,要和目標連接,連接成功以後就會觸發連接事件,然後寫入,讀取事件。(場景簡化了一下)
    • 非IO事件。這邊又分爲兩種:
      • 普通任務。使用execute提交的任務,直接執行的。
      • 調度任務。使用schedule提交的任務,一般需要延遲或者週期性執行的。
  • EventLoop在執行的時候,也是無線循環,循環體內主要有3件事:阻塞輪詢、執行IO事件和執行非IO事件。
    • 若當前沒有任務非IO事件(普通任務)需要執行,且在0.5s內沒有需要執行的調度任務的時候,先會進入一個無限循環,裏面會調用多路複用器的select(long)方法進行阻塞超時輪詢,阻塞超時默認是1s或者有定時任務的話,就取定時任務應該執行的時間與當前時間的間隔爲超時時間(意思就是,我超時結束的時候,最早的定時任務剛好可以執行了)。
    • 多路複用器的阻塞超時輪詢,並不會一直等到超時,有多種方式可以喚醒它:
      • 多路複用器已經準備好了至少一個事件;基本上就是有IO事件的話,就直接返回了,不會阻塞。
      • 使用wakeup方法。當其它線程調用的時候,會立刻喚醒正在阻塞輪詢多路複用器的線程。而EventLoop也是利用了這一點,當有新的任務提交進來,並且當前情況滿足4個條件的話,就會執行wakeUp。條件很好滿足。而且其中某些條件就是在判斷是不是在做阻塞輪詢,如果是的話,纔會去喚醒。
      • 當正在阻塞輪詢的時候,有新的非IO任務進來的話,就會立刻喚醒。和上一點是一回事,換了一種說法。
      • 這邊也有一個騷操作,它在執行一些中斷操作的時候,會提交一個空任務來喚醒。
      • 超時時間到。
      • 當前線程被中斷。後面這兩種沒有什麼可說的。
    • 它的這種喚醒機制,保證了不會影響到任何事件。但是仔細想想,這也是應該的,畢竟是它實在沒有事情做的時候,纔回去阻塞輪詢,因爲對於NIO而已,根本不需要進行阻塞,你去忙你的,忙完了回來叫我,我都給你準備好了,你忙你的,我做我的,相互不影響(你=eventLoop,我=多路複用器)。正因爲如此,它的代碼實現上面,對於跳出無限阻塞輪詢(阻塞輪詢外層有個無限循環)的條件也是非常開放(不知道怎麼描述了),很容易就跳出了,可以看看代碼。
    • 阻塞輪詢完了或者根本不需要阻塞輪詢的(有非IO事件),就要處理事件了。它這邊有個IO比例,默認是50,就是IO:非IO=50:50,比如處理IO的時間是100ms,那麼處理非IO的時間最大也得是100ms,但是它並沒有強行去限制,也確實不好做。它僅僅只是在每執行64個非IO事件以後去判斷一下這個時間,超了的話,就停下來。64,也不知道是怎麼定義的,說實話我覺得挺多的,太小的話,是不是就會影響到非IO任務的執行了呢?還有這個IO比例,當=100的時候,就完全忽略了時間比,每輪詢一次,就會把剩餘的所有非IO全部執行完。既然都是IO比例了,這種情況就不應該是隻執行IO嗎?只執行IO肯定不對,但是這個實現和對應的情況實在是不搭呀,理解不了。或許是因爲有些事情我還沒有理解透徹。
    • 執行IO的時候,就是把所有輪詢到的事件,挨個去執行。這塊就是我開篇提到的第四個核心,不過我還沒有細看(主要是挺複雜的,不花點事情是搞不明白的),就不說了。反正是一個一個執行IO事件,而且肯定是用當前線程去執行,但是肯定不會花太多時間去處理完的,到最後一定會交給另外一個EventLoopGroup,這也是標準的Reactor模型。netty服務端啓動的時候,需要提供兩個EventLoopGroup,也是這個作用吧,我猜的。
    • 執行非IO的時候,先把調度隊列中所有到期的取出來放進任務隊列中,然後挨個去執行。一個是全部執行完,一個有時間限制。執行完了以後,會執行tailTasks隊列裏面的任務,這個設計不知道用來幹嘛的,意思就是每一次輪詢結束,就去執行一下。感覺沒有什麼用呀。
    • 結束以後,下一波輪詢又開始了。
  • 它再內部阻塞輪詢多路複用器的時候,也修復了JDK的epoll bug。
    • bug描述:它會導致Selector空輪詢,IO線程CPU 100%,嚴重影響系統的安全性和可靠性。
    • 修復思路:
      • 根據該BUG的特徵,首先偵測該BUG是否發生:正常情況下,開始時間+阻塞輪詢時間<=當前時間;這個是正常的;但是如果反過來的話,就不正常了。實際上阻塞的時間比預期的時間會小,不符合javadoc的描述,就認爲做了一次空輪詢。當空輪詢次數超過默認值512次時,就去重新構建多路複用器。
      • 將問題Selector上註冊的Channel轉移到新建的Selector上
      • 老的問題Selector關閉,使用新建的Selector替換
  • 它內部還有一個比較重要的原子性的布爾值:wakeUp。它是用來確定是否需要喚醒正在使用阻塞輪詢多路複用器的線程(就是EventLoop的線程)。
    • true:代表應該被喚醒或者已經被喚醒了(它有的地方會判斷爲ture的時候,會立即喚醒,之後也不會修改它的狀態)
    • false:代表應該去阻塞輪詢了或者正在阻塞輪詢。
    • 修改的它的位置有3個:
      • 開始打算輪詢的時候,會置爲false(select(boolean)方法)。代表我馬上要阻塞輪詢了。
      • 在無限輪詢的循環體內,每次都會判斷:有新任務並且是false的時候,會置爲true,然後跳出。這個應該是來解決,當添加任務不滿足4個條件的時候,就不會觸發喚醒;這個是每次阻塞輪詢前判斷,也就是有的任務添加進來,雖然不會立即喚醒阻塞輪詢線程,但是當阻塞結束的時候,它一定就會跳出循環。結束,有新任務進來了。
      • 添加任務的時候,如果是false,會置爲true。wakeup(boolean inEventLoop)。添加任務需要觸發喚醒,需要滿足4個條件。

EventLoopGroup和EventLoop

在放一張結論圖:
在這裏插入圖片描述
到這邊就徹底結束了。

結語

不知道有沒有分享清楚我的學習過程以及EventLoop的運行機制?
不過我覺得還缺了一篇,結合IO模型分析EventLoop的使用場景,這樣會更好的體會到EventLoop的強大以及加深對它的瞭解。我覺得我應該會寫,但不會太快。

學習初衷很重要,在學習過程當中可以把握住重點,忽略掉一些干擾因素,提高自己的學習效率,但是也要記錄下來哪些應該學習而沒有學的。
學習方式也很重要,要知道自己怎麼去學習,學習之前獲取就要想明白一些。

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