1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Go 并發(fā)模型

        共 3377字,需瀏覽 7分鐘

         ·

        2023-08-24 21:45

        1. 調(diào)度模型

        CPU執(zhí)行指令的速度是非??斓?。在 3.0GHz 主頻的單核 CPU 核心上,大部分簡單指令的執(zhí)行僅需 1 個時鐘周期,也就是三分之一納秒。在僅考慮執(zhí)行,不考慮讀取數(shù)據(jù)耗時的情況下,1s 可以執(zhí)行 30 億條簡單指令,速度極快。CPU 慢的原因是慢在對外部數(shù)據(jù)的讀/寫上。外部 I/O 的速度慢和阻塞是導致 CPU 使用效率不高的最大原因。其實在大部分真實系統(tǒng)中,CPU 都不是瓶頸,CPU 的大部分時間被白白浪費了,所以增加 CPU 的吞吐量是程序員的重要指標。

        所謂提高 CPU 的有效吞吐量,就是讓 CPU 盡量多干活,而不是在空跑或等待。理想的狀態(tài)是機器的每個 CPU 核心都有事情做,盡可能快地做一些事情。

        • 盡可能讓每個 CPU 核心都有事情做。

        這就要求工作的線程要大于 CPU 的核心數(shù),單進程的程序最多使用一個 CPU 干活,沒有辦法有效利用機器資源。由于 CPU 要和外部設(shè)備通信,單個線程經(jīng)常會被阻塞,這其中包括 I/O 等待、缺頁中斷、等待網(wǎng)絡(luò)等。所以 CPU 和線程的比例是 1:1,大部分情況下不能充分發(fā)揮 CPU 的威力。實際上依據(jù)程序的特性,合理地調(diào)整 CPU 和線程的關(guān)系,一般情況下,線程數(shù)要大于 CPU 的個數(shù),才能發(fā)揮機器的價值。

        • 盡可能提高每個 CPU 核心做事情的效率

        現(xiàn)在操作系統(tǒng)雖然能夠進行并行調(diào)度,但當進程數(shù)大于 CPU 核心的時候,就存在進程切換的問題。該切換需要保存上下文,恢復堆棧。頻繁的切換也很耗時,我們的目標是盡量讓程序減少阻塞和切換,盡量讓進程跑滿操作系統(tǒng)分配的時間片。

        上面是從整個系統(tǒng)的角度來看程序的運行效率問題,但具體到應用程序又有所不同。應用程序的并發(fā)模型是多樣的,主要看以下三種。

        • 多進程模型

        進程都能被多核 CPU 并發(fā)調(diào)度,優(yōu)點是每個進程都有自己獨立的內(nèi)存空間,隔離性好、健壯性高;缺點是進程比較重,進程的切換消耗較大,進程間的通信需要多次在內(nèi)核區(qū)和用戶區(qū)之間復制數(shù)據(jù)。

        • 多線程模型

        這里的多線程是指啟動多個內(nèi)核線程進行處理,線程的優(yōu)點是通過共享內(nèi)存進行通信更快捷,切換代價?。蝗秉c是多個線程共享內(nèi)存空間,很容易導致數(shù)據(jù)訪問混亂,某個線程誤操作內(nèi)存掛掉可能危及整個線程組,健壯性不高。

        • 用戶級多線程模型

        用戶級多線程分為兩種情況,一種是 M:1 的方式,M 個用戶線程對應一個內(nèi)核線程,這種情況很容易因為一個系統(tǒng)阻塞,其他用戶線程都會阻塞,不能利用機器多核的優(yōu)勢。還有一種是 M:N 的方式,M 個用戶線程對應 N 個內(nèi)核線程,這種模式一般需要語言運行時或庫的支持,效率最高。

        程序并發(fā)處理的要求越來越高,但是不能無限制地增加系統(tǒng)線程數(shù),線程數(shù)過多會導致操作系統(tǒng)的調(diào)度開銷大,單個線程的單位時間內(nèi)被分配的運行時間片減少,單個線程的運行速度降低,單靠增加系統(tǒng)線程數(shù)不能滿足要求。為了不讓系統(tǒng)線程無限膨脹,于是就有了協(xié)程的概念。

        協(xié)程是一種用戶態(tài)的輕量級線程,協(xié)程的調(diào)度完全由用戶態(tài)程序控制,協(xié)程擁有自己的寄存器上下文和棧。協(xié)程調(diào)度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧,每個內(nèi)核線程可以對應多個用戶協(xié)程,當一個協(xié)程執(zhí)行體阻塞了,調(diào)度器會調(diào)度另一個協(xié)程執(zhí)行,最大效率地利用操作系統(tǒng)分給系統(tǒng)線程的時間片。我們提到的用戶級多線程模型就是一種協(xié)程模型,尤其以 M:N 模型最為高效。好處就是:

        1. 控制了系統(tǒng)線程數(shù),保證每個線程的運行時間片充足。

        2. 調(diào)度層能進行用戶態(tài)的切換,不會導致單個協(xié)程阻塞整個程序的情況,盡量減少上下文切換,提升運行效率。

        所以,協(xié)程是一種非常高效、理想的執(zhí)行模型。Go 的并發(fā)執(zhí)行模型就是變種的協(xié)程模型。

        2. 并發(fā)和調(diào)度

        Go 語言在語言層面引入了 goroutine。

        • goroutine 可以在用戶空間調(diào)度,避免了內(nèi)核態(tài)和用戶態(tài)切換導致的成本。

        • goroutine 是語言原生支持的,提供了非常簡潔的語法,屏蔽了大部分復雜底層實現(xiàn)。

        • goroutine 更小的??臻g允許用戶創(chuàng)建成千上萬的實例。

        Go 的調(diào)度模型中抽象出三個實體:M、P、G。

        G(Goroutine)

        G 是 Go 運行時對 goroutine 的抽象描述,G 中存放并發(fā)執(zhí)行的代碼入口地址、上下文、運行環(huán)境(關(guān)聯(lián)的 P 和 M)、運行棧等執(zhí)行相關(guān)的元信息。

        G 的新建、休眠、恢復、停止都受到 Go 運行時的管理。Go 運行時的監(jiān)控線程會監(jiān)控 G 的調(diào)度,G 不會長久地阻塞系統(tǒng)線程,運行時的調(diào)度器會自動切換到其他 G 上繼續(xù)執(zhí)行。G 新建或恢復時會添加到運行隊列,等待 M 取出并運行。

        M(Machine)

        M 代表 OS 內(nèi)核線程,是操作系統(tǒng)層面調(diào)度和執(zhí)行的實體。M 僅負責執(zhí)行,M 不停地被喚醒或創(chuàng)建,然后執(zhí)行。M 啟動時進入的是運行時的管理代碼,由這段代碼獲取 G 和 P 資源,然后執(zhí)行調(diào)度。另外,Go 語言運行時會單獨創(chuàng)建一個監(jiān)控線程,負責對程序的內(nèi)存、調(diào)度等信息進行監(jiān)控和控制。

        P(Processor)
        P 代表 M 運行 G 所需要的資源,是對資源的一種抽象和管理,P 不是一段代碼實體,而是一個管理的數(shù)據(jù)結(jié)構(gòu),P 主要是降低 M 管理調(diào)度 G 的復雜性,增加一個間接的控制層數(shù)據(jù)結(jié)構(gòu)。把 P 看作資源,而不是處理器,P 控制 Go代碼的并行度,它不是運行實體。P 持有 G 的隊列,P 可以隔離調(diào)度,解除 P 和 M 的綁定就解除了 M 對一串 G 的調(diào)用。P 在運行模型中只是一個數(shù)據(jù)模型,而不是程序控制模型。

        M 和 P 一起構(gòu)成一個運行時環(huán)境,每個 P 有一個本地的可調(diào)度 G 隊列,隊列里的 G 會被 M 依次調(diào)度執(zhí)行,如果本地隊列空了,則會去全局隊列偷取一部分 G,如果全局隊列也是空的,則去其他的 P 中偷取一部分 G。下面是調(diào)度結(jié)構(gòu)。

        e2eb74815b1e0cba648bfd3d7eada99c.webp


        G 并不是執(zhí)行體,而是用于存放并發(fā)執(zhí)行體的元信息,包括并發(fā)執(zhí)行的入口函數(shù)、堆棧、上下文等信息。G 對象是可以復用的,只需將相關(guān)元信息初始化為新值即可。M 僅負責執(zhí)行,M 啟動時進入運行時的管理代碼,這段管理代碼必須拿到可用的 P 后,才能執(zhí)行調(diào)度。P 的數(shù)目默認是 CPU 核心的數(shù)量,可以通過 runtime.GOMAXPROCS 函數(shù)設(shè)置或查詢,M 和 P 的數(shù)目差不多,運行時會根據(jù)當前的狀態(tài)動態(tài)地創(chuàng)建 M,M 有一個最大值上限,目前是 10000;G 與 P 是一種 M:N 的關(guān)系,M 可以成千上萬,遠遠大于 N。

        Go 啟動初始化過程

        • 分配和檢查??臻g

        • 初始化參數(shù)和環(huán)境變量

        • 當前運行線程標記為 m0,m0 是程序啟動的主線程

        • 調(diào)用運行時初始化函數(shù) runtime.schedinit 進行初始化。主要是初始化內(nèi)存空間分配器、GC、生成空閑 P 列表

        • 在 m0 上調(diào)度第一個 G,這個 G 運行 runtime.main 函數(shù)

        runtime.main 會拉起運行時的監(jiān)控線程,然后調(diào)用 main 包的 init() 初始化函數(shù),最后執(zhí)行 main 函數(shù)。

        什么時候創(chuàng)建 M、P、G

        程序啟動過程中會初始化空閑 P 列表,P 是在這個時候被創(chuàng)建的,同時第一個 G 也是在初始化過程中被創(chuàng)建的。后續(xù)在有 go 并發(fā)調(diào)用的地方都有可能創(chuàng)建 G。

        每個并發(fā)調(diào)用都會初始化一個新的 G 任務,然后喚醒 M 執(zhí)行任務。先嘗試獲取當前線程 M,如果無法獲取,則從全局調(diào)度的空閑 M 列表中獲取可用的 M,如果沒有可用的,則新建 M,然后綁定 P 和 G 進行運行。

        M 線程里有管理調(diào)度和切換堆棧的邏輯,但是 M 必須拿到 P 后才能運行,可以看到 M 是自驅(qū)動的,但是需要 P 的配合。

        瀏覽 42
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            啊灬啊别停灬用力啊太深在线视频 | 国产一级a毛一级a看免费视频黑人 | www.8x8x | 精品无码人妻一区二区免费蜜桃 | 被两个男人玩得很爽 | 国产一级三级 | 国产黄色视频网站在线观看 | 色久伊人 | 久久青青草大香蕉手机视频在线 | av电影波多野结衣 |