<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 進程 * [`[Doc]` Process (進程)](sections/process.md#process) * [`[Doc]` Child Processes (子進程)](sections/process.md#child-process) * [`[Doc]` Cluster (集群)](sections/process.md#cluster) * [`[Basic]` 進程間通信](sections/process.md#進程間通信) * [`[Basic]` 守護進程](sections/process.md#守護進程) ## 簡述 關于 Process, 我們需要討論的是兩個概念, ①操作系統的進程, ② Node.js 中的 Process 對象. 操作進程對于服務端而言, 好比 html 之于前端一樣基礎. 想做服務端編程是不可能繞過 Unix/Linux 的. 在 Linux/Unix/Mac 系統中運行 `ps -ef` 命令可以看到當前系統中運行的進程. 各個參數如下: |列名稱|意義| |-----|---| |UID|執行該進程的用戶ID| |PID|進程編號| |PPID|該進程的父進程編號| |C|該進程所在的CPU利用率| |STIME|進程執行時間| |TTY|進程相關的終端類型| |TIME|進程所占用的CPU時間| |CMD|創建該進程的指令| 關于進程以及操作系統一些更深入的細節推薦閱讀 APUE, 即《Unix 高級編程》等書籍來了解. ## Process 這里來討論 Node.js 中的 `process` 對象. 直接在代碼中通過 `console.log(process)` 即可打印出來. 可以看到 process 對象暴露了非常多有用的屬性以及方法, 具體的細節見[官方文檔](https://nodejs.org/dist/latest-v6.x/docs/api/process.html), 已經說的挺詳細了. 其中包括但不限于: * 進程基礎信息 * 進程 Usage * 進程級事件 * 依賴模塊/版本信息 * OS 基礎信息 * 賬戶信息 * 信號收發 * 三個標準流 ### process.nextTick 上一節已經提到過 `process.nextTick` 了, 這是一個你需要了解的, 重要的, 基礎方法. ``` ┌───────────────────────┐ ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ │ │ check │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ └──┤ close callbacks │ └───────────────────────┘ ``` `process.nextTick` 并不屬于 Event loop 中的某一個階段, 而是在 Event loop 的每一個階段結束后, 直接執行 `nextTickQueue` 中插入的 "Tick", 并且直到整個 Queue 處理完. 所以面試時又有可以問的問題了, 遞歸調用 process.nextTick 會怎么樣? (doge ```javascript function test() { process.nextTick(() => test()); } ``` 這種情況與以下情況, 有什么區別? 為什么? ```javascript function test() { setTimeout(() => test(), 0); } ``` ### 配置 配置是開發部署中一個很常見的問題. 普通的配置有兩種方式, 一是定義配置文件, 二是使用環境變量. ![node-configuration](https://blog-assets.risingstack.com/2016/Sep/node-js-survey/node-js-survey-envvar-config-new.png) 你可以通過[設置環境變量](http://cn.bing.com/search?q=linux+%E8%AE%BE%E7%BD%AE%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F)來指定配置, 然后通過 `process.env` 來獲取配置項. 另外也可以通過讀取定義好的配置文件來獲取, 在這方面有很多不錯的庫例如 `dotenv`, `node-config` 等, 而在使用這些庫來加載配置文件的時候, 通常都會碰到一個當前工作目錄的問題. > <a name="q-cwd"></a> 進程的當前工作目錄是什么? 有什么作用? 當前進程啟動的目錄, 通過 process.cwd() 獲取當前工作目錄 (current working directory), 通常是命令行啟動的時候所在的目錄 (也可以在啟動時指定), 文件操作等使用相對路徑的時候會相對當前工作目錄來獲取文件. 一些獲取配置的第三方模塊就是通過你的當前目錄來找配置文件的. 所以如果你錯誤的目錄啟動腳本, 可能沒法得到正確的結果. 在程序中可以通過 `process.chdir()` 來改變當前的工作目錄. ### 標準流 在 process 對象上還暴露了 `process.stderr`, `process.stdout` 以及 `process.stdin` 三個標準流, 熟悉 C/C++/Java 的同學應該對此比較熟悉. 關于這幾個流, 常見的面試問題是問 **console.log 是同步還是異步? 如何實現一個 console.log?** 如果簡歷中有出現 C/C++ 關鍵字, 一般都會問到如何實現一個同步的輸入 (類似實現C語言的 `scanf`, C++ 的 `cin`, Python 的 `raw_input` 等). ### 維護方面 熟悉與進程有關的基礎命令, 如 top, ps, pstree 等命令. ## Child Process 子進程 (Child Process) 是進程中一個重要的概念. 你可以通過 Node.js 的 `child_process` 模塊來執行可執行文件, 調用命令行命令, 比如其他語言的程序等. 也可以通過該模塊來將 .js 代碼以子進程的方式啟動. 比較有名的網易的分布式架構 [pomelo](https://github.com/NetEase/pomelo) 就是基于該模塊 (而不是 `cluster`) 來實現多進程分布式架構的. > <a name="q-fork"></a> child_process.fork 與 POSIX 的 fork 有什么區別? Node.js 的 `child_process.fork()` 不像 POSIX [fork(2)](http://man7.org/linux/man-pages/man2/fork.2.html) 系統調用, 不會拷貝當前父進程. 這里對于其他語言轉過的同學可能比較誤導, 可以作為一個比較偏的面試題. * spawn() 啟動一個子進程來執行命令 * options.detached 父進程死后是否允許子進程存活 * options.stdio 指定子進程的三個標準流 * spawnSync() 同步版的 spawn, 可指定超時, 返回的對象可獲得子進程的情況 * exec() 啟動一個子進程來執行命令, 帶回調參數獲知子進程的情況, 可指定進程運行的超時時間 * execSync() 同步版的 exec(), 可指定超時, 返回子進程的輸出 (stdout) * execFile() 啟動一個子進程來執行一個可執行文件, 可指定進程運行的超時時間 * execFileSync() 同步版的 execFile(), 返回子進程的輸出, 如何超時或者 exit code 不為 0, 會直接 throw Error * fork() 加強版的 spawn(), 返回值是 ChildProcess 對象可以與子進程交互 其中 exec/execSync 方法會直接調用 bash 來解釋命令, 所以如果有命令有外部參數, 則需要注意被注入的情況. ### child.kill 與 child.send 常見會問的面試題, 如 `child.kill` 與 `child.send` 的區別. 二者一個是基于信號系統, 一個是基于 IPC. > <a name="q-child"></a> 父進程或子進程的死亡是否會影響對方? 什么是孤兒進程? 子進程死亡不會影響父進程, 不過子進程死亡時(線程組的最后一個線程,通常是“領頭”線程死亡時),會向它的父進程發送死亡信號. 反之父進程死亡, 一般情況下子進程也會隨之死亡, 但如果此時子進程處于可運行態、僵死狀態等等的話, 子進程將被`進程1`(init 進程)收養,從而成為孤兒進程. 另外, 子進程死亡的時候(處于“終止狀態”),父進程沒有及時調用 `wait()` 或 `waitpid()` 來返回死亡進程的相關信息,此時子進程還有一個 `PCB` 殘留在進程表中,被稱作僵尸進程. ## Cluster Cluster 是常見的 Node.js 利用多核的辦法. 它是基于 `child_process.fork()` 實現的, 所以 cluster 產生的進程之間是通過 IPC 來通信的, 并且它也沒有拷貝父進程的空間, 而是通過加入 cluster.isMaster 這個標識, 來區分父進程以及子進程, 達到類似 POSIX 的 [fork](http://man7.org/linux/man-pages/man2/fork.2.html) 的效果. ```javascript const cluster = require('cluster'); // | | const http = require('http'); // | | const numCPUs = require('os').cpus().length; // | | 都執行了 // | | if (cluster.isMaster) { // |-|----------------- // Fork workers. // | for (var i = 0; i < numCPUs; i++) { // | cluster.fork(); // | } // | 僅父進程執行 (a.js) cluster.on('exit', (worker) => { // | console.log(`${worker.process.pid} died`); // | }); // | } else { // |------------------- // Workers can share any TCP connection // | // In this case it is an HTTP server // | http.createServer((req, res) => { // | res.writeHead(200); // | 僅子進程執行 (b.js) res.end('hello world\n'); // | }).listen(8000); // | } // |------------------- // | | console.log('hello'); // | | 都執行了 ``` 在上述代碼中 numCPUs 雖然是全局變量但是, 在父進程中修改它, 子進程中并不會改變, 因為父進程與子進程是完全獨立的兩個空間. 他們所謂的共有僅僅只是都執行了, 并不是同一份. 你可以把父進程執行的部分當做 `a.js`, 子進程執行的部分當做 `b.js`, 你可以把他們想象成是先執行了 `node a.js` 然后 cluster.fork 了幾次, 就執行執行了幾次 `node b.js`. 而 cluster 模塊則是二者之間的一個橋梁, 你可以通過 cluster 提供的方法, 讓其二者之間進行溝通交流. ### How It Works worker 進程是由 child_process.fork() 方法創建的, 所以可以通過 IPC 在主進程和子進程之間相互傳遞服務器句柄. cluster 模塊提供了兩種分發連接的方式. 第一種方式 (默認方式, 不適用于 windows), 通過時間片輪轉法(round-robin)分發連接. 主進程監聽端口, 接收到新連接之后, 通過時間片輪轉法來決定將接收到的客戶端的 socket 句柄傳遞給指定的 worker 處理. 至于每個連接由哪個 worker 來處理, 完全由內置的循環算法決定. 第二種方式是由主進程創建 socket 監聽端口后, 將 socket 句柄直接分發給相應的 worker, 然后當連接進來時, 就直接由相應的 worker 來接收連接并處理. 使用第二種方式時, 多個 worker 之間會存在競爭關系, 產生一個老生常談的 "[驚群效應](https://www.google.com.hk/search?q=%E6%83%8A%E7%BE%A4%E6%95%88%E5%BA%94)" 從而導致效率變低的問題. 該問題常見于 Apache. 并且各自競爭的情況下無法控制一個新的連接由哪個進程來處理, 從而導致各 worker 進程之間的負載不均衡, 比如通常 70% 的連接僅被 8 個進程中的 2 個處理, 而其他進程比較清閑. ## 進程間通信 IPC (Inter-process communication) 進程間通信技術. 常見的進程間通信技術列表如下: 類型|無連接|可靠|流控制|優先級 ---|-----|----|-----|----- 普通PIPE|N|Y|Y|N 命名PIPE|N|Y|Y|N 消息隊列|N|Y|Y|N 信號量|N|Y|Y|Y 共享存儲|N|Y|Y|Y UNIX流SOCKET|N|Y|Y|N UNIX數據包SOCKET|Y|Y|N|N Node.js 中的 IPC 通信是由 libuv 通過管道技術實現的, 在 windows 下由命名管道(named pipe)實現也就是上表中的最后第二個, *nix 系統則采用 UDS (Unix Domain Socket) 實現. 普通的 socket 是為網絡通訊設計的, 而網絡本身是不可靠的, 而為 IPC 設計的 socket 則不然, 因為默認本地的網絡環境是可靠的, 所以可以簡化大量不必要的 encode/decode 以及計算校驗等, 得到效率更高的 UDS 通信. 如果了解 Node.js 的 IPC 的話, 可以問個比較有意思的問題 > <a name="q-ipc-fd"></a> 在 IPC 通道建立之前, 父進程與子進程是怎么通信的? 如果沒有通信, 那 IPC 是怎么建立的? 這個問題也挺簡單, 只是個思路的問題. 在通過 child_process 建立子進程的時候, 是可以指定子進程的 env (環境變量) 的. 所以 Node.js 在啟動子進程的時候, 主進程先建立 IPC 頻道, 然后將 IPC 頻道的 fd (文件描述符) 通過環境變量 (`NODE_CHANNEL_FD`) 的方式傳遞給子進程, 然后子進程通過 fd 連上 IPC 與父進程建立連接. 最后于進程間通信 (IPC) 的問題, 一般不會直接問 IPC 的實現, 而是會問什么情況下需要 IPC, 以及使用 IPC 處理過什么業務場景等. ## 守護進程 最后的守護進程, 是服務端方面一個很基礎的概念了. 很多人可能只知道通過 pm2 之類的工具可以將進程以守護進程的方式啟動, 卻不了解什么是守護進程, 為什么要用守護進程. 對于水平好的同學, 我們是希望能了解守護進程的實現的. 普通的進程, 在用戶退出終端之后就會直接關閉. 通過 `&` 啟動到后臺的進程, 之后會由于會話(session組)被回收而終止進程. 守護進程是不依賴終端(tty)的進程, 不會因為用戶退出終端而停止運行的進程. ```c // 守護進程實現 (C語言版本) void init_daemon() { pid_t pid; int i = 0; if ((pid = fork()) == -1) { printf("Fork error !\n"); exit(1); } if (pid != 0) { exit(0); // 父進程退出 } setsid(); // 子進程開啟新會話, 并成為會話首進程和組長進程 if ((pid = fork()) == -1) { printf("Fork error !\n"); exit(-1); } if (pid != 0) { exit(0); // 結束第一子進程, 第二子進程不再是會話首進程 // 避免當前會話組重新與tty連接 } chdir("/tmp"); // 改變工作目錄 umask(0); // 重設文件掩碼 for (; i < getdtablesize(); ++i) { close(i); // 關閉打開的文件描述符 } return; } ``` [Node.js 編寫守護進程](https://cnodejs.org/topic/57adfadf476898b472247eac)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看