NodeJs 異步非阻塞

一般來說,高併發的解決方案就是提供多線程模型,服務器爲每個客戶端請求分配一個線程,使用同步 I/O,系統通過線程切換來彌補同步 I/O
調用的時間開銷。比如 Apache 就是這種策略,由於 I/O
一般都是耗時操作,因此這種策略很難實現高性能,但非常簡單,可以實現複雜的交互邏輯。

而事實上,大多數網站的服務器端都不會做太多的計算,它們接收到請求以後,把請求交給其它服務來處理(比如讀取數據庫),然後等着結果返回,最後再把結果發給客戶端。因此,Node.js
針對這一事實採用了單線程模型來處理,它不會爲每個接入請求分配一個線程,而是用一個主線程處理所有的請求,然後對 I/O
操作進行異步處理,避開了創建、銷燬線程以及在線程間切換所需的開銷和複雜性。

那麼在瞭解NodeJs異步非阻塞機制之前,你是否瞭解同步異步,阻塞和非阻塞?

同步與非同步:更面向非IO代碼
阻塞與非阻塞:更面向IO代碼

什麼是同步機制?

在這裏插入圖片描述
同步就是必須等待上一個函數執行完畢,才能繼續向下執行

什麼是異步機制?

在這裏插入圖片描述
主線程執行到類IO操作時,會調用底層封裝的libuv分配線程執行,並放入隊列中等待主線程循環事件抽取,詳細的後面會講到

什麼是阻塞機制?

在這裏插入圖片描述
顧名思義,必須等待前一個得到結果後才能繼續執行,和同步容易混淆,但阻塞主要針對的是IO操作

什麼是非阻塞機制?

在這裏插入圖片描述

非阻塞,意味着不用等待前一個IO操作返回就可以直接執行其他的IO操作

想必大家已經對這些概念有了一定了解,就讓我們繼續向下閱讀

衆所周知,JavaScript是單線程執行的,也就是說所有的非IO請求代碼都會在主線程中同步執行,但是當我們發起IO請求時,該IO請求就不是在主線程中執行了,不然主線程就會被阻塞調,無法響應其他事件,看下圖


在這裏插入圖片描述

NodeJs是異步IO調用,根據上圖我們所得,當我們發起IO請求時,調用的是各個不同平臺的操作系統內部實現的線程池內的線程,這裏的IO請求不僅僅是讀寫文件,在unix中,將計算機抽象了一層,磁盤文件,硬件,套接字等幾乎所有計算機資源都被抽象爲文件,即IO請求就是抽象後的文件

NodeJs基於libuv的架構示意圖:
在這裏插入圖片描述
什麼是libuv?

Libuv是一個高性能的,事件驅動的異步I/O庫,它本身是由C語言編寫的,具有很高的可移植性。libuv封裝了不同平臺底層對於異步IO模型的實現,所以它還本身具備着Windows, Linux都可使用的跨平臺能力。
https://juejin.im/post/5d412865e51d4561e84fcba7

什麼是IOCP?

是windows支持多個同時發生的異步I/O操作的應用程序編程接口

根據上圖所得,Node是基於libuv封裝層運行來實現跨平臺兼容的,所有平臺兼容性的判斷都由這一層來完成,並保證Node程序與unix和IOCP之間各自獨立,Node在編譯期間會判斷平臺條件,選擇性編譯unix目錄或windows目錄下的原文件到目錄程序中。

具體異步IO實現圖:
在這裏插入圖片描述

仔細查看上圖我們發現:
構成NodeJs異步IO模型主要分四大要素

  • 事件循環
  • 觀察者
  • 請求對象
  • IO線程池
           
    主線程操作
    發起異步IO調用,將請求參數(param, path, callback)等信息封裝到請求對象上,然後將請求對象放入請求隊列中,等待線程池給該請求分配可用線程
           
    線程池操作:
    如果線程池中有可用的線程,則取出請求隊列內請求對象並分配線程,在分配的線程內執行對象中的IO操作,執行完成後將執行結果封裝到請求對象中,通知線程池IO操作已經完成,然後將該線程還給線程池
           
    事件循環操作:
    底層使用了while(true)機制獲取已完成IO操作的事件,並觸發該事件,相對應的IO事件觀察者會獲取該請求對象(此時該請求對象已經涵蓋了callback, param等),IO觀察者取出callback和IO執行結果並調用執行函數callback

總結

Node.js 在主線程裏維護了一個事件隊列,當接到請求後,就將該請求作爲一個事件放入這個隊列中,然後繼續接收其他請求。當主線程空閒時(沒有請求接入時),就開始循環事件隊列,檢查隊列中是否有要處理的事件,這時要分兩種情況:如果是非 I/O 任務,就通過主線程處理,並通過回調函數返回到上層調用;如果是 I/O 任務,就從 線程池 中拿出一個線程來處理這個事件,並通過觀察者指定回調函數,然後繼續循環隊列中的其他事件。
當線程中的 I/O 任務完成以後,通過觀察者執行指定的回調函數,並把這個完成的事件放到事件隊列的尾部,等待事件循環,當主線程再次循環到該事件時,就直接處理並返回給上層調用

參考文章:
https://juejin.im/post/5d412865e51d4561e84fcba7
https://www.cnblogs.com/onepixel/p/7143769.html
https://juejin.im/post/5d21f7e9e51d455071250b81
https://juejin.im/post/5af1413ef265da0b851cce80

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