亚洲最大看欧美片,亚洲图揄拍自拍另类图片,欧美精品v国产精品v呦,日本在线精品视频免费

  • 站長(zhǎng)資訊網(wǎng)
    最全最豐富的資訊網(wǎng)站

    聊聊Node.js中的進(jìn)程、線程、協(xié)程與并發(fā)模型

    聊聊Node.js中的進(jìn)程、線程、協(xié)程與并發(fā)模型

    Node.js 現(xiàn)在已成為構(gòu)建高并發(fā)網(wǎng)絡(luò)應(yīng)用服務(wù)工具箱中的一員,何以 Node.js 會(huì)成為大眾的寵兒?本文將從進(jìn)程、線程、協(xié)程、I/O 模型這些基本概念說(shuō)起,為大家全面介紹關(guān)于 Node.js 與并發(fā)模型的這些事。

    進(jìn)程

    我們一般將某個(gè)程序正在運(yùn)行的實(shí)例稱之為進(jìn)程,它是操作系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)基本單元,一般包含以下幾個(gè)部分:

    • 程序:即要執(zhí)行的代碼,用于描述進(jìn)程要完成的功能;
    • 數(shù)據(jù)區(qū)域:進(jìn)程處理的數(shù)據(jù)空間,包括數(shù)據(jù)、動(dòng)態(tài)分配的內(nèi)存、處理函數(shù)的用戶棧、可修改的程序等信息;
    • 進(jìn)程表項(xiàng):為了實(shí)現(xiàn)進(jìn)程模型,操作系統(tǒng)維護(hù)著一張稱為進(jìn)程表的表格,每個(gè)進(jìn)程占用一個(gè)進(jìn)程表項(xiàng)(也叫進(jìn)程控制塊),該表項(xiàng)包含了程序計(jì)數(shù)器、堆棧指針、內(nèi)存分配情況、所打開(kāi)文件的狀態(tài)、調(diào)度信息等重要的進(jìn)程狀態(tài)信息,從而保證進(jìn)程掛起后,操作系統(tǒng)能夠正確地重新喚起該進(jìn)程。

    進(jìn)程具有以下特征:

    • 動(dòng)態(tài)性:進(jìn)程的實(shí)質(zhì)是程序在多道程序系統(tǒng)中的一次執(zhí)行過(guò)程,進(jìn)程是動(dòng)態(tài)產(chǎn)生,動(dòng)態(tài)消亡的;
    • 并發(fā)性:任何進(jìn)程都可以同其他進(jìn)程一起并發(fā)執(zhí)行;
    • 獨(dú)立性:進(jìn)程是一個(gè)能獨(dú)立運(yùn)行的基本單位,同時(shí)也是系統(tǒng)分配資源和調(diào)度的獨(dú)立單位;
    • 異步性:由于進(jìn)程間的相互制約,使進(jìn)程具有執(zhí)行的間斷性,即進(jìn)程按各自獨(dú)立的、不可預(yù)知的速度向前推進(jìn)。

    需要注意的是,如果一個(gè)程序運(yùn)行了兩遍,即便操作系統(tǒng)能夠使它們共享代碼(即只有一份代碼副本在內(nèi)存中),也不能改變正在運(yùn)行的程序的兩個(gè)實(shí)例是兩個(gè)不同的進(jìn)程的事實(shí)。

    在進(jìn)程的執(zhí)行過(guò)程中,由于中斷、CPU 調(diào)度等各種原因,進(jìn)程會(huì)在下面幾個(gè)狀態(tài)中切換:

    聊聊Node.js中的進(jìn)程、線程、協(xié)程與并發(fā)模型

    • 運(yùn)行態(tài):此刻進(jìn)程正在運(yùn)行,并占用了 CPU;
    • 就緒態(tài):此刻進(jìn)程已準(zhǔn)備就緒,隨時(shí)可以運(yùn)行,但因?yàn)槠渌M(jìn)程正在運(yùn)行而被暫時(shí)停止;
    • 阻塞態(tài):此刻進(jìn)程處于阻塞狀態(tài),除非某個(gè)外部事件(比如鍵盤(pán)輸入的數(shù)據(jù)已到達(dá))發(fā)生,否則進(jìn)程將不能運(yùn)行。

    通過(guò)上面的進(jìn)程狀態(tài)切換圖可知,進(jìn)程可以從運(yùn)行態(tài)切換成就緒態(tài)和阻塞態(tài),但只有就緒態(tài)才能直接切換成運(yùn)行態(tài),這是因?yàn)椋?/p>

    • 從運(yùn)行態(tài)切換成就緒態(tài)是由進(jìn)程調(diào)度程序引起的,因?yàn)橄到y(tǒng)認(rèn)為當(dāng)前進(jìn)程已經(jīng)占用了過(guò)多的 CPU 時(shí)間,決定讓其它進(jìn)程使用 CPU 時(shí)間;并且進(jìn)程調(diào)度程序是操作系統(tǒng)的一部分,進(jìn)程甚至感覺(jué)不到調(diào)度程序的存在;
    • 從運(yùn)行態(tài)切換成阻塞態(tài)是由進(jìn)程自身原因(比如等待用戶的鍵盤(pán)輸入)導(dǎo)致進(jìn)程無(wú)法繼續(xù)執(zhí)行,只能掛起等待某個(gè)事件(比如鍵盤(pán)輸入的數(shù)據(jù)已到達(dá))發(fā)生;當(dāng)相關(guān)事件發(fā)生時(shí),進(jìn)程先轉(zhuǎn)換為就緒態(tài),如果此時(shí)沒(méi)有其它進(jìn)程運(yùn)行,則立刻轉(zhuǎn)換為運(yùn)行態(tài),否則進(jìn)程將維持就緒態(tài),等待進(jìn)程調(diào)度程序的調(diào)度。

    線程

    有些時(shí)候,我們需要使用線程來(lái)解決以下問(wèn)題:

    • 隨著進(jìn)程數(shù)量的增加,進(jìn)程之間切換的成本將越來(lái)越大,CPU 的有效使用率也會(huì)越來(lái)越低,嚴(yán)重情況下可能造成系統(tǒng)假死等現(xiàn)象;
    • 每個(gè)進(jìn)程都有自己獨(dú)立的內(nèi)存空間,且各個(gè)進(jìn)程之間的內(nèi)存空間是相互隔離的,而某些任務(wù)之間可能需要共享一些數(shù)據(jù),多個(gè)進(jìn)程之間的數(shù)據(jù)同步就過(guò)于繁瑣。

    關(guān)于線程,我們需要知道以下幾點(diǎn):

    • 線程是程序執(zhí)行中的一個(gè)單一順序控制流,是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位,它包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)行單位;
    • 一個(gè)進(jìn)程中可以包含多個(gè)線程,每個(gè)線程并行執(zhí)行不同的任務(wù);
    • 一個(gè)進(jìn)程中的所有線程共享進(jìn)程的內(nèi)存空間(包括代碼、數(shù)據(jù)、堆等)以及一些資源信息(比如打開(kāi)的文件和系統(tǒng)信號(hào));
    • 一個(gè)進(jìn)程中的線程在其它進(jìn)程中不可見(jiàn)。

    了解了線程的基本特征,下面我們來(lái)聊一下常見(jiàn)的幾種線程類型。

    內(nèi)核態(tài)線程

    內(nèi)核態(tài)線程是直接由操作系統(tǒng)支持的線程,其主要特點(diǎn)如下:

    • 線程的創(chuàng)建、調(diào)度、同步、銷毀由系統(tǒng)內(nèi)核完成,但其開(kāi)銷較為昂貴;
    • 內(nèi)核可將內(nèi)核態(tài)線程映射到各個(gè)處理器上,能夠輕松做到一個(gè)處理器核心對(duì)應(yīng)一個(gè)內(nèi)核線程,從而充分地競(jìng)爭(zhēng)與利用 CPU 資源;
    • 僅能訪問(wèn)內(nèi)核的代碼和數(shù)據(jù);
    • 資源同步與數(shù)據(jù)共享效率低于進(jìn)程的資源同步與數(shù)據(jù)共享效率。

    用戶態(tài)線程

    用戶態(tài)線程是完全建立在用戶空間的線程,其主要特點(diǎn)如下:

    • 線程的創(chuàng)建、調(diào)度、同步、銷毀由用戶空間完成,其開(kāi)銷非常低;
    • 由于用戶態(tài)線程由用戶空間維護(hù),內(nèi)核根本感知不到用戶態(tài)線程的存在,因此內(nèi)核僅對(duì)其所屬的進(jìn)程做調(diào)度及資源分配,而進(jìn)程中線程的調(diào)度及資源分配由程序自行處理,這很可能造成一個(gè)用戶態(tài)線程被阻塞在系統(tǒng)調(diào)用中,則整個(gè)進(jìn)程都將會(huì)阻塞的風(fēng)險(xiǎn);
    • 能夠訪問(wèn)所屬進(jìn)程的所有共享地址空間和系統(tǒng)資源;
    • 資源同步與數(shù)據(jù)共享效率較高。

    輕量級(jí)進(jìn)程(LWP)

    輕量級(jí)進(jìn)程(LWP)是建立在內(nèi)核之上并由內(nèi)核支持的用戶線程,其主要特點(diǎn)如下:

    • 用戶空間只能通過(guò)輕量級(jí)進(jìn)程(LWP)來(lái)使用內(nèi)核線程,可看作是用戶態(tài)線程與內(nèi)核線程的橋接器,因此只有先支持內(nèi)核線程,才能有輕量級(jí)進(jìn)程(LWP);

    • 大多數(shù)輕量級(jí)進(jìn)程(LWP)的操作,都需要用戶態(tài)空間發(fā)起系統(tǒng)調(diào)用,此系統(tǒng)調(diào)用的代價(jià)相對(duì)較高(需要在用戶態(tài)與內(nèi)核態(tài)之間進(jìn)行切換);

    • 每個(gè)輕量級(jí)進(jìn)程(LWP)都需要與一個(gè)特定的內(nèi)核線程關(guān)聯(lián),因此:

      • 與內(nèi)核線程一樣,可在全系統(tǒng)范圍內(nèi)充分地競(jìng)爭(zhēng)與利用 CPU 資源;
      • 每個(gè)輕量級(jí)進(jìn)程(LWP)都是一個(gè)獨(dú)立的線程調(diào)度單元,這樣即使有一個(gè)輕量級(jí)進(jìn)程(LWP)在系統(tǒng)調(diào)用中被阻塞,也不影響整個(gè)進(jìn)程的執(zhí)行;
      • 輕量級(jí)進(jìn)程(LWP)需要消耗內(nèi)核資源(主要指內(nèi)核線程的??臻g),這樣導(dǎo)致系統(tǒng)中不可能支持大量的輕量級(jí)進(jìn)程(LWP);
    • 能夠訪問(wèn)所屬進(jìn)程的所有共享地址空間和系統(tǒng)資源。

    小結(jié)

    上文我們對(duì)常見(jiàn)的線程類型(內(nèi)核態(tài)線程、用戶態(tài)線程、輕量級(jí)進(jìn)程)進(jìn)行了簡(jiǎn)單介紹,它們各自有各自的適用范圍,在實(shí)際的使用中可根據(jù)自己的需要自由地對(duì)其進(jìn)行組合使用,比如常見(jiàn)的一對(duì)一、多對(duì)一、多對(duì)多等模型,由于篇幅限制,本文對(duì)此不做過(guò)多介紹,感興趣的同學(xué)可自行研究。

    協(xié)程

    協(xié)程(Coroutine),也叫纖程(Fiber),是一種建立在線程之上,由開(kāi)發(fā)者自行管理執(zhí)行調(diào)度、狀態(tài)維護(hù)等行為的一種程序運(yùn)行機(jī)制,其特點(diǎn)主要有:

    • 因執(zhí)行調(diào)度無(wú)需上下文切換,故具有良好的執(zhí)行效率;
    • 因運(yùn)行在同一線程,故不存在線程通信中的同步問(wèn)題;
    • 方便切換控制流,簡(jiǎn)化編程模型。

    在 JavaScript 中,我們經(jīng)常用到的 async/await 便是協(xié)程的一種實(shí)現(xiàn),比如下面的例子:

    function updateUserName(id, name) {   const user = getUserById(id);   user.updateName(name);   return true; }  async function updateUserNameAsync(id, name) {   const user = await getUserById(id);   await user.updateName(name);   return true; }

    上例中,函數(shù) updateUserNameupdateUserNameAsync 內(nèi)的邏輯執(zhí)行順序是:

    • 調(diào)用函數(shù) getUserById 并將其返回值賦給變量 user;
    • 調(diào)用 userupdateName 方法;
    • 返回 true 給調(diào)用者。

    兩者的主要區(qū)別在于其實(shí)際運(yùn)行過(guò)程中的狀態(tài)控制:

    • 在函數(shù) updateUserName 的執(zhí)行過(guò)程中,按照前文所述的邏輯順序依次執(zhí)行;
    • 在函數(shù) updateUserNameAsync 的執(zhí)行過(guò)程中,同樣按照前文所述的邏輯順序依次執(zhí)行,只不過(guò)在遇到 await 時(shí),updateUserNameAsync 將會(huì)被掛起并保存掛起位置當(dāng)前的程序狀態(tài),直到 await 后面的程序片段返回后,才會(huì)再次喚醒 updateUserNameAsync 并恢復(fù)掛起前的程序狀態(tài),然后繼續(xù)執(zhí)行下一段程序。

    通過(guò)上面的分析我們可以大膽猜測(cè):協(xié)程要解決的并非是進(jìn)程、線程要解決的程序并發(fā)問(wèn)題,而是要解決處理異步任務(wù)時(shí)所遇到的問(wèn)題(比如文件操作、網(wǎng)絡(luò)請(qǐng)求等);在 async/await 之前,我們只能通過(guò)回調(diào)函數(shù)來(lái)處理異步任務(wù),這很容易使我們陷入回調(diào)地獄,生產(chǎn)出一坨坨屎一般難以維護(hù)的代碼,通過(guò)協(xié)程,我們便可以實(shí)現(xiàn)異步代碼同步化的目的。

    需要牢記的是:協(xié)程的核心能力是能夠?qū)⒛扯纬绦驋炱鸩⒕S護(hù)程序掛起位置的狀態(tài),并在未來(lái)某個(gè)時(shí)刻在掛起的位置恢復(fù),并繼續(xù)執(zhí)行掛起位置后的下一段程序。

    I/O 模型

    一個(gè)完整的 I/O 操作需要經(jīng)歷以下階段:

    • 用戶進(jìn)(線)程通過(guò)系統(tǒng)調(diào)用向內(nèi)核發(fā)起 I/O 操作請(qǐng)求;
    • 內(nèi)核對(duì) I/O 操作請(qǐng)求進(jìn)行處理(分為準(zhǔn)備階段和實(shí)際執(zhí)行階段),并將處理結(jié)果返回給用戶進(jìn)(線)程。

    我們可將 I/O 操作大致分為阻塞 I/O、非阻塞 I/O同步 I/O、異步 I/O 四種類型,在討論這些類型之前,我們先熟悉下以下兩組概念(此處假設(shè)服務(wù) A 調(diào)用了服務(wù) B):

    • 阻塞/非阻塞

      • 如果 A 只有在接收到 B 的響應(yīng)之后才返回,那么該調(diào)用為阻塞調(diào)用;
      • 如果 A 調(diào)用 B 后立即返回(即無(wú)需等待 B 執(zhí)行完畢),那么該調(diào)用為非阻塞調(diào)用。
    • 同步/異步

      • 如果 B 只有在執(zhí)行完之后再通知 A,那么服務(wù) B 是同步的;
      • 如果 A 調(diào)用 B 后,B 立刻給 A 一個(gè)請(qǐng)求已接收的通知,然后在執(zhí)行完之后通過(guò)回調(diào)的方式將執(zhí)行結(jié)果通知給 A,那么服務(wù) B 就是異步的。

    很多人經(jīng)常將阻塞/非阻塞同步/異步搞混淆,故需要特別注意:

    • 阻塞/非阻塞針對(duì)于服務(wù)的調(diào)用者而言;
    • 同步/異步針對(duì)于服務(wù)的被調(diào)用者而言。

    了解了阻塞/非阻塞同步/異步,我們來(lái)看具體的 I/O 模型。

    阻塞 I/O

    定義:用戶進(jìn)(線)程發(fā)起 I/O 系統(tǒng)調(diào)用后,用戶進(jìn)(線)程會(huì)被立即阻塞,直到整個(gè) I/O 操作處理完畢并將結(jié)果返回給用戶進(jìn)(線)程后,用戶進(jìn)(線)程才能解除阻塞狀態(tài),繼續(xù)執(zhí)行后續(xù)操作。

    特點(diǎn):

    • 由于該模型會(huì)阻塞用戶進(jìn)(線)程,因此該模型不占用 CPU 資源;
    • 在執(zhí)行 I/O 操作的時(shí)候,用戶進(jìn)(線)程不能進(jìn)行其它操作;
    • 該模型僅適用于并發(fā)量小的應(yīng)用,這是因?yàn)橐粋€(gè) I/O 請(qǐng)求就能阻塞進(jìn)(線)程,所以為了能夠及時(shí)響應(yīng) I/O 請(qǐng)求,需要為每個(gè)請(qǐng)求分配一個(gè)進(jìn)(線)程,這樣會(huì)造成巨大的資源占用,并且對(duì)于長(zhǎng)連接請(qǐng)求來(lái)說(shuō),由于進(jìn)(線)程資源長(zhǎng)期得不到釋放,如果后續(xù)有新的請(qǐng)求,將會(huì)產(chǎn)生嚴(yán)重的性能瓶頸。

    非阻塞 I/O

    定義:

    • 用戶進(jìn)(線)程發(fā)起 I/O 系統(tǒng)調(diào)用后,如果該 I/O 操作未準(zhǔn)備就緒,該 I/O 調(diào)用將會(huì)返回一個(gè)錯(cuò)誤,用戶進(jìn)(線)程也無(wú)需等待,而是通過(guò)輪詢的方式來(lái)檢測(cè)該 I/O 操作是否就緒;
    • 操作就緒后,實(shí)際的 I/O 操作會(huì)阻塞用戶進(jìn)(線)程直到執(zhí)行結(jié)果返回給用戶進(jìn)(線)程。

    特點(diǎn):

    • 由于該模型需要用戶進(jìn)(線)程不斷地詢問(wèn) I/O 操作就緒狀態(tài)(一般使用 while 循環(huán)),因此該模型需占用 CPU,消耗 CPU 資源;
    • I/O 操作就緒前,用戶進(jìn)(線)程不會(huì)阻塞,等到 I/O 操作就緒后,后續(xù)實(shí)際的 I/O 操作將阻塞用戶進(jìn)(線)程;
    • 該模型僅適用于并發(fā)量小,且不需要及時(shí)響應(yīng)的應(yīng)用。

    同(異)步 I/O

    用戶進(jìn)(線)程發(fā)起 I/O 系統(tǒng)調(diào)用后,如果該 I/O 調(diào)用會(huì)導(dǎo)致用戶進(jìn)(線)程阻塞,那么該 I/O 調(diào)用便為同步 I/O,否則為 異步 I/O

    判斷 I/O 操作同步異步的標(biāo)準(zhǔn)是用戶進(jìn)(線)程與 I/O 操作的通信機(jī)制,其中:

    • 同步情況下用戶進(jìn)(線)程與 I/O 的交互是通過(guò)內(nèi)核緩沖區(qū)進(jìn)行同步的,即內(nèi)核會(huì)將 I/O 操作的執(zhí)行結(jié)果同步到緩沖區(qū),然后再將緩沖區(qū)的數(shù)據(jù)復(fù)制到用戶進(jìn)(線)程,這個(gè)過(guò)程會(huì)阻塞用戶進(jìn)(線)程,直到 I/O 操作完成;
    • 異步情況下用戶進(jìn)(線)程與 I/O 的交互是直接通過(guò)內(nèi)核進(jìn)行同步的,即內(nèi)核會(huì)直接將 I/O 操作的執(zhí)行結(jié)果復(fù)制到用戶進(jìn)(線)程,這個(gè)過(guò)程不會(huì)阻塞用戶進(jìn)(線)程。

    Node.js 的并發(fā)模型

    Node.js 采用的是單線程、基于事件驅(qū)動(dòng)的異步 I/O 模型,個(gè)人認(rèn)為之所以選擇該模型的原因在于:

    • JavaScript 在 V8 下以單線程模式運(yùn)行,為其實(shí)現(xiàn)多線程極其困難;
    • 絕大多數(shù)網(wǎng)絡(luò)應(yīng)用都是 I/O 密集型的,在保證高并發(fā)的情況下,如何合理、高效地管理多線程資源相對(duì)于單線程資源的管理更加復(fù)雜。

    總之,本著簡(jiǎn)單、高效的目的,Node.js 采用了單線程、基于事件驅(qū)動(dòng)的異步 I/O 模型,并通過(guò)主線程的 EventLoop 和輔助的 Worker 線程來(lái)實(shí)現(xiàn)其模型:

    • Node.js 進(jìn)程啟動(dòng)后,Node.js 主線程會(huì)創(chuàng)建一個(gè) EventLoop,EventLoop 的主要作用是注冊(cè)事件的回調(diào)函數(shù)并在未來(lái)的某個(gè)事件循環(huán)中執(zhí)行;
    • Worker 線程用來(lái)執(zhí)行具體的事件任務(wù)(在主線程之外的其它線程中以同步方式執(zhí)行),然后將執(zhí)行結(jié)果返回到主線程的 EventLoop 中,以便 EventLoop 執(zhí)行相關(guān)事件的回調(diào)函數(shù)。

    需要注意的是,Node.js 并不適合執(zhí)行 CPU 密集型(即需要大量計(jì)算)任務(wù);這是因?yàn)?EventLoop 與 JavaScript 代碼(非異步事件任務(wù)代碼)運(yùn)行在同一線程(即主線程),它們中任何一個(gè)如果運(yùn)行時(shí)間過(guò)長(zhǎng),都可能導(dǎo)致主線程阻塞,如果應(yīng)用程序中包含大量需要長(zhǎng)時(shí)間執(zhí)行的任務(wù),將會(huì)降低服務(wù)器的吞吐量,甚至可能導(dǎo)致服務(wù)器無(wú)法響應(yīng)。

    總結(jié)

    Node.js 是前端開(kāi)發(fā)人員現(xiàn)在乃至未來(lái)不得不面對(duì)的技術(shù),然而大多數(shù)前端開(kāi)發(fā)人員對(duì) Node.js 的認(rèn)知僅停留在表面,為了讓大家更好地理解 Node.js 的并發(fā)模型,本文先介紹了進(jìn)程、線程、協(xié)程,接著介紹了不同的 I/O 模型,最后對(duì) Node.js 的并發(fā)模型進(jìn)行了簡(jiǎn)單介紹。雖然介紹 Node.js 并發(fā)模型的篇幅不多,但筆者相信萬(wàn)變不離其宗,掌握了相關(guān)基礎(chǔ),再深入理解 Node.js 的設(shè)計(jì)與實(shí)現(xiàn)必將事半功倍。

    最后,本文若有紕漏之處,還望大家能夠指正,祝大家快樂(lè)編碼每一天。

    贊(0)
    分享到: 更多 (0)
    網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)