91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

nodejs是單進程嗎

發布時間:2021-11-11 16:41:05 來源:億速云 閱讀:271 作者:iii 欄目:web開發

這篇文章主要講解了“nodejs是單進程嗎”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“nodejs是單進程嗎”吧!

nodejs是單進程。node.js采用單線程異步非阻塞模式,也就是說每一個計算獨占cpu,遇到“I/O”請求不阻塞后面的計算,當“I/O”完成后,以事件的方式通知,繼續執行下一個計算。

nodejs是單進程嗎

本教程操作環境:windows7系統、nodejs 12.19.0版、Dell G3電腦。

一、多線程與單線程

像java、python這個可以具有多線程的語言。多線程同步模式是這樣的,將cpu分成幾個線程,每個線程同步運行。

nodejs是單進程嗎

而node.js采用單線程異步非阻塞模式,也就是說每一個計算獨占cpu,遇到I/O請求不阻塞后面的計算,當I/O完成后,以事件的方式通知,繼續執行計算2。

nodejs是單進程嗎

事件驅動、異步、單線程、非阻塞I/O,這是我們聽得最多的關于nodejs的介紹。看到上面的關鍵字,可能我們會好奇:

為什么在瀏覽器中運行的 Javascript 能與操作系統進行如此底層的交互?
nodejs既然是單線程,如何實現異步、非阻塞I/O?
nodejs全是異步調用和非阻塞I/O,就真的不用管并發數了嗎?
nodejs事件驅動是如何實現的?和瀏覽器的event loop是一回事嗎?
nodejs擅長什么?不擅長什么?

二、nodejs內部揭秘

要弄清楚上面的問題,首先要弄清楚nodejs是怎么工作的。

nodejs是單進程嗎

我們可以看到,Node.js 的結構大致分為三個層次:

1、    Node.js 標準庫,這部分是由 Javascript 編寫的,即我們使用過程中直接能調用的 API。在源碼中的 lib 目錄下可以看到。

2、    Node bindings,這一層是 Javascript 與底層 C/C++ 能夠溝通的關鍵,前者通過 bindings 調用后者,相互交換數據。

3、這一層是支撐 Node.js 運行的關鍵,由 C/C++ 實現。
V8:Google 推出的 Javascript VM,也是 Node.js 為什么使用的是 Javascript 的關鍵,它為 Javascript 提供了在非瀏覽器端運行的環境,它的高效是 Node.js 之所以高效的原因之一。
Libuv:它為 Node.js 提供了跨平臺,線程池,事件池,異步 I/O 等能力,是 Node.js 如此強大的關鍵。
C-ares:提供了異步處理 DNS 相關的能力。
http_parser、OpenSSL、zlib 等:提供包括 http 解析、SSL、數據壓縮等其他的能力。

三、libuv簡介

nodejs是單進程嗎

可以看出,幾乎所有和操作系統打交道的部分都離不開 libuv的支持。libuv也是node實現跨操作系統的核心所在。

四、我們再來看看最開始我拋出的問題

問題一:為什么在瀏覽器中運行的 Javascript 能與操作系統進行如此底層的交互?

舉個簡單的例子,我們想要打開一個文件,并進行一些操作,可以寫下面這樣一段代碼:

var fs = require('fs');
fs.open('./test.txt', "w", function(err, fd) {
    //..do something
});

fs.open = function(path, flags, mode, callback) { 
    // ...
    binding.open(pathModule._makeLong(path),
                        stringToFlags(flags),
 mode,
 callback); 
};

這段代碼的調用過程大致可描述為:lib/fs.js → src/node_file.cc →uv_fs

nodejs是單進程嗎

從JavaScript調用Node的核心模塊,核心模塊調用C++內建模塊,內建模塊通過   libuv進行系統調用,這是Node里經典的調用方式。總體來說,我們在 Javascript 中調用的方法,最終都會通過node-bindings 傳遞到 C/C++ 層面,最終由他們來執行真正的操作。Node.js 即這樣與操作系統進行互動。

問題二:nodejs既然是單線程,如何實現異步、非阻塞I/O?

順便回答標題nodejs真的是單線程嗎?其實只有js執行是單線程,I/O顯然是其它線程。
js執行線程是單線程,把需要做的I/O交給libuv,自己馬上返回做別的事情,然后libuv在指定的時刻回調就行了。其實簡化的流程就是醬紫的!細化一點,nodejs會先從js代碼通過node-bindings調用到C/C++代碼,然后通過C/C++代碼封裝一個叫 “請求對象” 的東西交給libuv,這個請求對象里面無非就是需要執行的功能+回調之類的東西,給libuv執行以及執行完實現回調。

總結來說,一個異步 I/O 的大致流程如下:

1、發起 I/O 調用
用戶通過 Javascript 代碼調用 Node 核心模塊,將參數和回調函數傳入到核心模塊;
Node 核心模塊會將傳入的參數和回調函數封裝成一個請求對象;
將這個請求對象推入到 I/O 線程池等待執行;
Javascript 發起的異步調用結束,Javascript 線程繼續執行后續操作。

2、執行回調
I/O 操作完成后,會取出之前封裝在請求對象中的回調函數,執行這個回調函數,以完成 Javascript 回調的目的。(這里回調的細節下面講解)

nodejs是單進程嗎

從這里,我們可以看到,我們其實對 Node.js 的單線程一直有個誤會。事實上,它的單線程指的是自身 Javascript 運行環境的單線程,Node.js 并沒有給 Javascript 執行時創建新線程的能力,最終的實際操作,還是通過 Libuv 以及它的事件循環來執行的。這也就是為什么 Javascript 一個單線程的語言,能在 Node.js 里面實現異步操作的原因,兩者并不沖突。

問題三:nodejs全是異步調用和非阻塞I/O,就真的不用管并發數了嗎?

之前我們就提到了線程池的概念,發現nodejs并不是單線程的,而且還有并行事件發生。同時,線程池默認大小是 4 ,也就是說,同時能有4個線程去做文件i/o的工作,剩下的請求會被掛起等待直到線程池有空閑。 所以nodejs對于并發數,是由限制的。
線程池的大小可以通過 UV_THREADPOOL_SIZE 這個環境變量來改變 或者在nodejs代碼中通過 process.env.UV_THREADPOOL_SIZE來重新設置。

問題四:nodejs事件驅動是如何實現的?和瀏覽器的event loop是一回事嗎?

event loop是一個執行模型,在不同的地方有不同的實現。瀏覽器和nodejs基于不同的技術實現了各自的event loop。

簡單來說:

nodejs的event是基于libuv,而瀏覽器的event loop則在html5的規范中明確定義。
libuv已經對event loop作出了實現,而html5規范中只是定義了瀏覽器中event loop的模型,具體實現留給了瀏覽器廠商。

我們上面提到了libuv接過了js傳遞過來的 I/O請求,那么何時來處理回調呢?

libuv有一個事件循環(event loop)的機制,來接受和管理回調函數的執行。

event loop是libuv的核心所在,上面我們提到 js 會把回調和任務交給libuv,libuv何時來調用回調就是 event loop 來控制的。event loop 首先會在內部維持多個事件隊列(或者叫做觀察者 watcher),比如 時間隊列、網絡隊列等等,使用者可以在watcher中注冊回調,當事件發生時事件轉入pending狀態,再下一次循環的時候按順序取出來執行,而libuv會執行一個相當于 while true的無限循環,不斷的檢查各個watcher上面是否有需要處理的pending狀態事件,如果有則按順序去觸發隊列里面保存的事件,同時由于libuv的事件循環每次只會執行一個回調,從而避免了 競爭的發生。Libuv的 event loop執行圖:

nodejs是單進程嗎

nodejs的event loop分為6個階段,每個階段的作用如下:
timers:執行setTimeout() 和 setInterval()中到期的callback。
I/O callbacks:上一輪循環中有少數的I/Ocallback會被延遲到這一輪的這一階段執行
idle, prepare:僅內部使用
poll:最為重要的階段,執行I/O callback,在適當的條件下會阻塞在這個階段
check:執行setImmediate的callback
close callbacks:執行close事件的callback,例如socket.on("close",func)

event loop的每一次循環都需要依次經過上述的階段。  每個階段都有自己的callback隊列,每當進入某個階段,都會從所屬的隊列中取出callback來執行,當隊列為空或者被執行callback的數量達到系統的最大數量時,進入下一階段。這六個階段都執行完畢稱為一輪循環。

附帶event loop 源碼:

int uv_run(uv_loop_t* loop, uv_run_mode mode) {
    int timeout;
    int r;
    int ran_pending;
  
    /*
    從uv__loop_alive中我們知道event loop繼續的條件是以下三者之一:
    1,有活躍的handles(libuv定義handle就是一些long-lived objects,例如tcp server這樣)
    2,有活躍的request
    3,loop中的closing_handles
    */
    r = uv__loop_alive(loop);
    if (!r)
      uv__update_time(loop);
  
    while (r != 0 && loop->stop_flag == 0) {
      uv__update_time(loop);//更新時間變量,這個變量在uv__run_timers中會用到
      uv__run_timers(loop);//timers階段
      ran_pending = uv__run_pending(loop);//從libuv的文檔中可知,這個其實就是I/O callback階段,ran_pending指示隊列是否為空
      uv__run_idle(loop);//idle階段
      uv__run_prepare(loop);//prepare階段
  
      timeout = 0;
  
      /**
      設置poll階段的超時時間,以下幾種情況下超時會被設為0,這意味著此時poll階段不會被阻塞,在下面的poll階段我們還會詳細討論這個
      1,stop_flag不為0
      2,沒有活躍的handles和request
      3,idle、I/O callback、close階段的handle隊列不為空
      否則,設為timer階段的callback隊列中,距離當前時間最近的那個
      **/    
      if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
        timeout = uv_backend_timeout(loop);
  
      uv__io_poll(loop, timeout);//poll階段
      uv__run_check(loop);//check階段
      uv__run_closing_handles(loop);//close階段
      //如果mode == UV_RUN_ONCE(意味著流程繼續向前)時,在所有階段結束后還會檢查一次timers,這個的邏輯的原因不太明確
      
      if (mode == UV_RUN_ONCE) {
        uv__update_time(loop);
        uv__run_timers(loop);
      }
  
      r = uv__loop_alive(loop);
      if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
        break;
    }
  
    if (loop->stop_flag != 0)
      loop->stop_flag = 0;
  
    return r;
  }

這里我們再詳細了解一下poll階段:

poll 階段有兩個主要功能:
1、執行下限時間已經達到的timers的回調
2、處理 poll 隊列里的事件。

當event loop進入 poll 階段,并且 沒有設定的timers(there are no timers scheduled),會發生下面兩件事之一:

1、如果 poll 隊列不空,event loop會遍歷隊列并同步執行回調,直到隊列清空或執行的回調數到達系統上限;

2、如果 poll 隊列為空,則發生以下兩件事之一:
(1)如果代碼已經被setImmediate()設定了回調, event loop將結束 poll 階段進入 check 階段來執行 check 隊列(里的回調)。
(2)如果代碼沒有被setImmediate()設定回調,event loop將阻塞在該階段等待回調被加入 poll 隊列,并立即執行。

但是,當event loop進入 poll 階段,并且 有設定的timers,一旦 poll 隊列為空(poll 階段空閑狀態):
event loop將檢查timers,如果有1個或多個timers的下限時間已經到達,event loop將繞回 timers 階段。

event loop的一個例子講述:

var fs = require('fs');

function someAsyncOperation (callback) {
  // 假設這個任務要消耗 95ms
  fs.readFile('/path/to/file', callback);
}

var timeoutScheduled = Date.now();

setTimeout(function () {

  var delay = Date.now() - timeoutScheduled;

  console.log(delay + "ms have passed since I was scheduled");
}, 100);

// someAsyncOperation要消耗 95 ms 才能完成
someAsyncOperation(function () {

  var startCallback = Date.now();

  // 消耗 10ms...
  while (Date.now() - startCallback < 10) {
    ; // do nothing
  }

});

當event loop進入 poll 階段,它有個空隊列(fs.readFile()尚未結束)。所以它會等待剩下的毫秒,直到最近的timer的下限時間到了。當它等了95ms,fs.readFile()首先結束了,然后它的回調被加到 poll的隊列并執行——這個回調耗時10ms。之后由于沒有其它回調在隊列里,所以event loop會查看最近達到的timer的下限時間,然后回到 timers 階段,執行timer的回調。

所以在示例里,回調被設定 和 回調執行間的間隔是105ms。

到這里我們再總結一下,整個異步IO的流程:

nodejs是單進程嗎

問題五、nodejs擅長什么?不擅長什么?

Node.js 通過 libuv 來處理與操作系統的交互,并且因此具備了異步、非阻塞、事件驅動的能力。因此,NodeJS能響應大量的并發請求。所以,NodeJS適合運用在高并發、I/O密集、少量業務邏輯的場景。

上面提到,如果是 I/O 任務,Node.js 就把任務交給線程池來異步處理,高效簡單,因此 Node.js 適合處理I/O密集型任務。但不是所有的任務都是 I/O 密集型任務,當碰到CPU密集型任務時,即只用CPU計算的操作,比如要對數據加解密(node.bcrypt.js),數據壓縮和解壓(node-tar),這時 Node.js 就會親自處理,一個一個的計算,前面的任務沒有執行完,后面的任務就只能干等著 。我們看如下代碼:

var start = Date.now();//獲取當前時間戳
setTimeout(function () {
    console.log(Date.now() - start);
    for (var i = 0; i < 1000000000; i++){//執行長循環
    }
}, 1000);
setTimeout(function () {
    console.log(Date.now() - start);
}, 2000);

最終我們的打印結果是:(結果可能因為你的機器而不同)
1000
3738

對于我們期望2秒后執行的setTimeout函數其實經過了3738毫秒之后才執行,換而言之,因為執行了一個很長的for循環,所以我們整個Node.js主線程被阻塞了,如果在我們處理100個用戶請求中,其中第一個有需要這樣大量的計算,那么其余99個就都會被延遲執行。如果操作系統本身就是單核,那也就算了,但現在大部分服務器都是多 CPU 或多核的,而 Node.js 只有一個 EventLoop,也就是只占用一個 CPU 內核,當 Node.js 被CPU 密集型任務占用,導致其他任務被阻塞時,卻還有 CPU 內核處于閑置狀態,造成資源浪費。

其實雖然Node.js可以處理數以千記的并發,但是一個Node.js進程在某一時刻其實只是在處理一個請求。

因此,Node.js 并不適合 CPU 密集型任務。

感謝各位的閱讀,以上就是“nodejs是單進程嗎”的內容了,經過本文的學習后,相信大家對nodejs是單進程嗎這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

芦溪县| 蓝山县| 开江县| 南部县| 黎平县| 思茅市| 林州市| 清苑县| 普定县| 嫩江县| 绥中县| 辰溪县| 乌恰县| 景德镇市| 平塘县| 洛宁县| 鄂尔多斯市| 嵩明县| 卢龙县| 明水县| 赣州市| 昌乐县| 镇雄县| 中山市| 兴义市| 蕉岭县| 仙桃市| 吉安县| 松滋市| 鄂托克前旗| 镇赉县| 桦南县| 互助| 静海县| 察雅县| 泸州市| 长垣县| 象山县| 昆明市| 丹东市| 罗山县|