<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Netty之線程喚醒wakeup

          共 4085字,需瀏覽 9分鐘

           ·

          2021-04-13 10:10


          首先回顧下, Netty中的IO線程主要完成三件事




          IO線程三件事


          輪詢IO事件

          處理IO事件

          執(zhí)行任務



          在輪詢IO事件的過程中,在Linux系統(tǒng)下, 使用epoll實現(xiàn).

          涉及的Netty代碼如下







          private void select() {

              // ...

              int selectedKeys =

                        selector.select(timeoutMillis);

              // ...

          }

          具體源碼位置:

          io.netty.channel.nio.NioEventLoop#select



          多路復用

          常見的多路復用實現(xiàn)所依賴的系統(tǒng)調用方法包括select(),poll(),epoll(). 不同的平臺, 具體的系統(tǒng)調用方法也不同.



          當IO線程執(zhí)行以上代碼的時候, 如果超時時間timeoutMillis還沒有到達的情況下, IO線程就會處于阻塞狀態(tài). 這個時候如果非IO線程需要向對端寫數(shù)據(jù), 由于Netty是異步的框架, 它的實現(xiàn)是非IO線程將寫數(shù)據(jù)封裝成一個任務提交到IO線程的任務隊列里.




          當任務提交到任務隊列后, 那么就會面臨一個問題.此時的IO線程處于阻塞狀態(tài), 是否需要喚醒它呢? 

          答案是需要喚醒, 之所以要把它喚醒, 是需要讓IO線程可以及時的處理剛剛非IO線程提交的任務. 






          @Override
          protected void wakeup(boolean inEventLoop) {
             if (!inEventLoop && wakenUp.compareAndSet(false, true)) {
                 // 喚醒IO線程
                 selector.wakeup();
             }
          }

          源碼位置: io.netty.channel.nio.NioEventLoop#wakeup


          以上代碼, 就是喚醒的代碼, 主要調用的方法就是wakeup.





          IO線程調用select方法被阻塞, 非IO線程通過調用wakeup方法將IO線程喚醒.





          接下來通過查看它的系統(tǒng)調用, 弄清楚它到底是如何實現(xiàn)的.


          環(huán)境


          1. Oracle VM VirtualBox

          2. 5.10.0-kali3-amd64



          代碼如下


          // WakeUp.javaimport java.net.InetSocketAddress;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;
          public class WakeUp {
          public static void main(String[] args) throws Exception {
          ServerSocketChannel serverSocketChannel;
          Selector selector = Selector.open(); serverSocketChannel = ServerSocketChannel.open();
          serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1", 8080), 64); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
          new Thread() { @Override public void run() { try { System.out.print("Thread[" + Thread.currentThread().getName() + "]invoke select\r\n");                    // 底層調用epoll_wait而阻塞 int readyChannels = selector.select(); } catch (Exception x) { x.printStackTrace(); } System.out.print("Success...\r\n"); } }.start();
                  // 之所以設置的時間比較久, 是為了讓程序暫時不結束 Thread.sleep(5_60_000); System.out.print("Thread[" + Thread.currentThread().getName() + "]invoke wakeup\r\n"); // 喚醒阻塞線程 selector.wakeup();
              }}


          以上代碼的邏輯比較簡單, 一個線程調用select()方法阻塞, 另一個線程喚醒它.

          首先javac編譯以上代碼, 然后使用一個查看系統(tǒng)調用的命令strace.


          strace -ff -o strace java WakeUp



          具體如何使用strace請童鞋自行Google


          執(zhí)行以后, 通過以下步驟進行分析



          01


          查看進程ID



          使用jps查看進程ID號



          獲得PID=2114



          02


          查看進程打開的文件描述符



          進入 /proc/2114/fd目錄下, 就可以查看到當前進程(PID=2114)打開的文件描述符




          0,1,2這三個文件描述符是標準輸入,標準輸出和錯誤輸出.

          4號文件描述符是在使用epoll實現(xiàn)的多路復用IO創(chuàng)建的一個文件描述符.

          5,6這兩個文件描述符是一對管道.

          7,8這兩個文件描述符是一對套接字.




          03


          查看系統(tǒng)調用



          在上面執(zhí)行strace命令的時候, 在它的同目錄下會生成如下文件



          通過搜索strace命令打印的文件內容, 查看具體的系統(tǒng)調用方法.


          使用grep命令搜索關鍵字pipe

          程序調用pipe這個系統(tǒng)調用創(chuàng)建管道. 

          其中的5和6是兩個文件描述符,也就是在/proc/2114/fd目錄下的那兩個5和6文件描述符.

          5這個描述符用來讀取數(shù)據(jù), 6這個描述符用來寫入數(shù)據(jù), 這樣就實現(xiàn)了兩個進程之間的通信.


          使用grep命令搜索關鍵字socket

          程序調用socketpair這個系統(tǒng)調用創(chuàng)建套接字.

          其中的8和9是兩個文件描述符,也就是在/proc/2114/fd目錄下的那兩個8和9文件描述符. 8這個描述符用來讀取數(shù)據(jù), 9這個描述符用來寫入數(shù)據(jù), 這樣就實現(xiàn)了兩個進程之間的通信.


          微助點
          補充

          平時使用的socket系統(tǒng)調用是用來創(chuàng)建可以網(wǎng)絡進程間通信的套接字, 而這里的socketpair系統(tǒng)調用是用來創(chuàng)建本機進程間通信的套接字, 它不會走網(wǎng)絡協(xié)議棧.


          微助點
          進程通信

          進程通信方式有很多, 比如管道, FIFO, 消息隊列, 共享存儲, 信號, Socket等.




          epoll簡介


          epoll三個關鍵的方法: epoll_create,epoll_ctl,epoll_wait.

          epoll_create用于創(chuàng)建epoll文件描述符

          epoll_ctl用于管理其他文件描述符

          epoll_wait用于阻塞等待其他文件描述符就緒.



          使用grep命令搜索關鍵字epoll

          通過epoll_create創(chuàng)建4號文件描述符.

          5和7這兩個文件描述符添加到epoll上(底層是添加到內核的紅黑樹).



          在上面的Java代碼中, 當調用int readyChannels = selector.select()方法的時候, 底層就會調用epoll_wait方法, 那么線程就會阻塞在此.

          當另一個線程調用selector.wakeup()的時候, 它就會向6號文件描述符寫入數(shù)據(jù), 通過pipe通信的方式, 喚醒另一個阻塞的線程.

          可以通過grep搜索關鍵字write驗證結論.

          通過write系統(tǒng)調用向6號文件描述符寫入數(shù)據(jù), 具體數(shù)據(jù)沒有任何含義, 它就是想喚醒阻塞的線程.  與6號文件描述符對應的是5號文件描述符. 由于epoll管理著5號文件描述符, 這樣epoll發(fā)現(xiàn)有文件描述符就緒(5號文件描述符就緒), 被阻塞的線程也就會被操作系統(tǒng)重新調度.






          簡單介紹了Netty中IO線程如何阻塞和被喚醒的底層系統(tǒng)調用.


          瀏覽 49
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产视频久久免费 | 国产精品麻豆三级三级视频 | 亚洲黄色小电影 | 91香蕉 | 久久狼人综合 |