<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>

          寫了一年golang,來(lái)聊聊進(jìn)程、線程與協(xié)程

          共 2257字,需瀏覽 5分鐘

           ·

          2021-10-16 08:59

          進(jìn)程

          在早期的單任務(wù)計(jì)算機(jī)中,用戶一次只能提交一個(gè)作業(yè),獨(dú)享系統(tǒng)的全部資源,同時(shí)也只能干一件事情。進(jìn)行計(jì)算時(shí)不能進(jìn)行 IO 讀寫,但 CPU 與 IO 的速度存在巨大差異,一個(gè)作業(yè)在 CPU 上所花費(fèi)的時(shí)間非常少,大部分時(shí)間在等待 IO。

          為了更合理的利用 CPU 資源,把內(nèi)存劃分為多塊,不同程序使用各自的內(nèi)存空間互不干擾,這里單獨(dú)的程序就是一個(gè)進(jìn)程,CPU 可以在多個(gè)進(jìn)程之間切換執(zhí)行,讓 CPU 的利用率變高。

          為了實(shí)現(xiàn) CPU 在多個(gè)進(jìn)程之間切換,需要保存進(jìn)程的上下文(如程序計(jì)數(shù)器、棧、內(nèi)核數(shù)據(jù)結(jié)構(gòu)等等),以便下次切換回來(lái)可以恢復(fù)執(zhí)行。還需要一種調(diào)度算法,Linux 中采用了基于時(shí)間片和優(yōu)先級(jí)的完全公平調(diào)度算法。

          線程

          多進(jìn)程的出現(xiàn)是為了解決 CPU 利用率的問(wèn)題,那為什么還需要線程?答案是為了減少上下文切換時(shí)的開(kāi)銷

          進(jìn)程在如下兩個(gè)時(shí)間點(diǎn)可能會(huì)讓出 CPU,進(jìn)行 CPU 切換:

          • 進(jìn)程阻塞,如網(wǎng)絡(luò)阻塞、代碼層面的阻塞(如鎖)、系統(tǒng)調(diào)用等
          • 進(jìn)程時(shí)間片用完,讓出 CPU

          而進(jìn)程切換 CPU 時(shí)需要進(jìn)行這兩步:

          • 切換頁(yè)目錄以使用新的地址空間
          • 切換內(nèi)核棧和硬件上下文

          進(jìn)程和線程在 Linux 中沒(méi)有本質(zhì)區(qū)別,他們最大的不同就是進(jìn)程有自己獨(dú)立的內(nèi)存空間,而線程(同進(jìn)程中)是共享內(nèi)存空間。

          在進(jìn)程切換時(shí)需要轉(zhuǎn)換內(nèi)存地址空間,而線程切換沒(méi)有這個(gè)動(dòng)作,所以線程切換比進(jìn)程切換代價(jià)更小。

          為什么內(nèi)存地址空間轉(zhuǎn)換這么慢?Linux 實(shí)現(xiàn)中,每個(gè)進(jìn)程的地址空間都是虛擬的,虛擬地址空間轉(zhuǎn)換到物理地址空間需要查頁(yè)表,這個(gè)查詢是很慢的過(guò)程,因此會(huì)用一種叫做 TLB 的 cache 來(lái)加速,當(dāng)進(jìn)程切換后,TLB 也隨之失效了,所以會(huì)變慢。

          綜上,線程是為了降低進(jìn)程切換過(guò)程中的開(kāi)銷。

          協(xié)程

          當(dāng)我們的程序是 IO 密集型時(shí)(如 web 服務(wù)器、網(wǎng)關(guān)等),為了追求高吞吐,有兩種思路:

          1. 為每個(gè)請(qǐng)求開(kāi)一個(gè)線程處理,為了降低線程的創(chuàng)建開(kāi)銷,可以使用線程池技術(shù),理論上線程池越大,則吞吐越高,但線程池越大,CPU 花在切換上的開(kāi)銷也越大

          線程的創(chuàng)建、銷毀都需要調(diào)用系統(tǒng)調(diào)用,每次請(qǐng)求都創(chuàng)建,高并發(fā)下開(kāi)銷就顯得很大,而且線程占用內(nèi)存是 MB 級(jí)別,數(shù)量不能太多

          為什么線程越多 cpu 切換越多?準(zhǔn)確來(lái)說(shuō)是可執(zhí)行的線程越多,cpu 切換越多,因?yàn)椴僮飨到y(tǒng)的調(diào)度要保證絕對(duì)公平,有可執(zhí)行線程時(shí),一定是要雨露均沾,所以切換次數(shù)變多

          1. 使用異步非阻塞的開(kāi)發(fā)模型,用一個(gè)進(jìn)程或線程接收請(qǐng)求,然后通過(guò) IO 多路復(fù)用讓進(jìn)程或線程不阻塞,省去上下文切換的開(kāi)銷

          這兩個(gè)方案,優(yōu)缺點(diǎn)都很明顯,方案1實(shí)現(xiàn)簡(jiǎn)單,但性能不高;方案2性能非常好,但實(shí)現(xiàn)起來(lái)復(fù)雜。有沒(méi)有介于這兩者之間的方案?既要簡(jiǎn)單,又要性能高,協(xié)程就解決了這個(gè)問(wèn)題。

          協(xié)程是用戶視角的一種抽象,操作系統(tǒng)并沒(méi)有這個(gè)概念,其主要思想是在用戶態(tài)實(shí)現(xiàn)調(diào)度算法,用少量線程完成大量任務(wù)的調(diào)度。

          協(xié)程需要解決線程遇到的幾個(gè)問(wèn)題:

          • 內(nèi)存占用要小,且創(chuàng)建開(kāi)銷要小
          • 減少上下文切換的開(kāi)銷

          第一點(diǎn)好實(shí)現(xiàn),用戶態(tài)的協(xié)程,只是一個(gè)數(shù)據(jù)結(jié)構(gòu),無(wú)需系統(tǒng)調(diào)用,而且可以設(shè)計(jì)的很小,達(dá)到 KB 級(jí)別。

          第二點(diǎn)只能減少上下文切換次數(shù)來(lái)解決,因?yàn)閰f(xié)程的本質(zhì)還是線程,其切換開(kāi)銷在用戶態(tài)是無(wú)法降低的,只能通過(guò)降低切換次數(shù)來(lái)達(dá)到總體上開(kāi)銷的減少,可以有如下手段:

          1. 讓可執(zhí)行的線程盡量少,這樣切換次數(shù)必然會(huì)少
          2. 讓線程盡可能的處于運(yùn)行狀態(tài),而不是阻塞讓出時(shí)間片

          Goroutine

          goroutine 是 golang 實(shí)現(xiàn)的協(xié)程,其特點(diǎn)是在語(yǔ)言層面就支持,使用起來(lái)非常方便,它的核心是MPG調(diào)度模型:

          • M:內(nèi)核線程
          • P:處理器,用來(lái)執(zhí)行 goroutine,它維護(hù)了本地可運(yùn)行隊(duì)列
          • G:goroutine,代碼和數(shù)據(jù)結(jié)構(gòu)
          • S:調(diào)度器,維護(hù)M和P的信息

          除此之外還有一個(gè)全局可運(yùn)行隊(duì)列。

          1. 在 golang 中使用 go 關(guān)鍵字啟動(dòng)一個(gè) goroutine,它將會(huì)被掛到 P 的 runqueue 中,等待被調(diào)度
          1. 當(dāng) M0 中正在運(yùn)行的 G0 阻塞時(shí)(如執(zhí)行了一個(gè)系統(tǒng)調(diào)用),此時(shí) M0 會(huì)休眠,它將放棄掛載的 P0,以便被其他 M 調(diào)度到
          1. 當(dāng) M0 系統(tǒng)調(diào)用結(jié)束后,會(huì)嘗試“偷”一個(gè) P,如果不成功,M0 將 G0 放到全局的 runqueue 中

          2. P 會(huì)定期檢查全局 runqueue,保證自己消化完 G 后有事可做,同時(shí)也會(huì)從其他 P 里“偷” G

          從上述看來(lái),MPG 模型似乎只限制了同時(shí)運(yùn)行的線程數(shù),但上下文切換只發(fā)生在可運(yùn)行的線程上,應(yīng)該是有一定的作用,當(dāng)然這只是一部分。

          golang 在 runtime 層面攔截了可能導(dǎo)致線程阻塞的情況,并針對(duì)性優(yōu)化,他們可分為兩類:

          • 網(wǎng)絡(luò) IO、channel 操作、鎖:只阻塞 G,M、P 可用,即線程不會(huì)讓出時(shí)間片
          • 系統(tǒng)調(diào)用:阻塞 M,P 需要切換,線程會(huì)讓出時(shí)間片

          所以綜合來(lái)看,goroutine 會(huì)比線程切換開(kāi)銷少。

          總結(jié)

          從單進(jìn)程到多進(jìn)程提高了 CPU 利用率;從進(jìn)程到線程,降低了上下文切換的開(kāi)銷;從線程到協(xié)程,進(jìn)一步降低了上下文切換的開(kāi)銷,使得高并發(fā)的服務(wù)可以使用簡(jiǎn)單的代碼寫出來(lái),技術(shù)的每一步發(fā)展都是為了解決實(shí)際問(wèn)題。


          搜索關(guān)注微信公眾號(hào)"捉蟲大師",后端技術(shù)分享,架構(gòu)設(shè)計(jì)、性能優(yōu)化、源碼閱讀、問(wèn)題排查、踩坑實(shí)踐。

          瀏覽 36
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  青娱乐色色 | 伊人日产无码中文久久久 | 怕怕视频2017 | 日本在线人妻 | 国产精品性爱视频 |