02.13 GO 語言之 Goroutine 原理解析

併發 一個 CPU 上能同時執行多項任務,在很短時間內,CPU 來回切換任務執行(在某段很短時間內執行 程序 a,然後又迅速得切換到程序 b 去執行),有時間上的重疊(宏觀上是同時的,微觀仍是順序執行),這樣看起來多個任務像是同時執行,這就是併發。

並行 當系統有多個 CPU 時,每個 CPU 同一時刻都運行任務,互不搶佔自己所在的 CPU 資源,同時進行, 稱為並行。

進程 CPU 在切換程序的時候,如果不保存上一個程序的狀態(context --上下文),直接切換下一個程 序,就會丟失上一個程序的一系列狀態,於是引入了進程這個概念,用以劃分好程序運行時所需 要的資源。因此進程就是一個程序運行時候的所需要的基本資源單位(也可以說是程序運行的一 個實體)。

線程 CPU 切換多個進程的時候,會花費不少的時間,因為切換進程需要切換到內核態,而每次調度需 要內核態都需要讀取用戶態的數據,進程一旦多起來,CPU 調度會消耗一大堆資源,因此引入了 線程的概念,線程本身幾乎不佔有資源,他們共享進程裡的資源,內核調度起來不會那麼像進程 切換那麼耗費資源。協程 協程擁有自己的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其他地方,在 切回來的時候,恢復先前保存的寄存器上下文和棧。因此,協程能保留上一次調用時的狀態(即 所有局部狀態的一個特定組合),每次過程重入時,就相當於進入上一次調用的狀態,換種說 法:進入上一次離開時所處邏輯流的位置。線程和進程的操作是由程序觸發系統接口,最後的執 行者是系統;協程的操作執行者則是用戶自身程序,goroutine 也是協程。


Go 併發模式

生產者消費者模型

  • 該模式主要通過平衡生產線程和消費線程的工作能力來提高程序的整體處理數據的速度。
  • 生產者生產一些數據,然後放到成果隊列中,同時消費者從成果隊列中來取這些數據。這樣 就讓生產消費變成了異步的兩個過程。
  • 當成果隊列中沒有數據時,消費者就進入飢餓的等待中;而當成果隊列中數據已滿時,生產 者則面臨因產品擠壓導致 CPU 被剝奪的下崗問題。
<code>packagemain
import(
"fmt"
"time"
)
// 生產者: 生成 factor 整數倍的序列
funcProducer(factorint, outchanfori :=0; ; i++ {
out }
}
// 消費者
funcConsumer(in forv :=rangein {
fmt.Println(v)
}
}

funcmain(){
ch :=make(chanint,64)// 成果隊列
goProducer(3, ch)// 生成 3 的倍數的序列
goProducer(5, ch)// 生成 5 的倍數的序列
goConsumer(ch)// 消費 生成的隊列
// 運行一定時間後退出
time.Sleep(5* time.Second)
}/<code>


Go 併發模型

Go 語言的併發處理參考了 CSP(Communicating Sequential Process 通訊順序進程)模型。CSP 併發模型是 Hoare 在 1978 年提出的 CSP 的概念,不同於傳統的多線程通過共享內存來通信, CSP 有著精確的數學模型,並實際應用在了 Hoare 參與設計的 T9000 通用計算機上。CSP 講究的是“以通信的方式來共享內存”。

Don’t communicate by sharing memory; instead, share memory by communicating. 不要通過共享內存來通信,而應通過通信來共享內存。


Go 的 CSP 模型實現與原始的 CSP 實現有點差別:原始的 CSP 中 channel 裡的任務都是立即執行的, 而 go 語言為其增加了一個緩存,即任務可以先暫存起來,等待執行線程準備好再順序執行。

Go 的 CSP 併發模型,是通過 goroutine 和 channel 來實現的。

  • goroutine 是 Go 語言中併發的執行單位。有點抽象,其實就是和傳統概念上的”線程“類似, 可以理解為”線程“。
  • channel 是 Go 語言中各個併發結構體(goroutine)之前的通信機制。通俗的講,就是各個 goroutine 之間通信的”管道“,有點類似於Linux 中的管道。

生成一個 goroutine 的方式非常的簡單:Go 一下,就生成了。

<code>gof()/<code>

通信機制 channel 也很方便,傳數據用 channel


Go 調度器 GMP

Go 語言運行時環境提供了非常強大的管理 goroutine 和系統內核線程的調度器, 內部提供了三種 對象:Goroutine,Machine,Processor。

Goroutine : 指應用創建的 goroutine

Machine : 指系統內核線程。

Processor : 指承載多個 goroutine 的運行器

在宏觀上說,Goroutine 與 Machine 因為 Processor 的存在,形成了多對多(M:N)的關係。M個 用戶線程對應N個系統線程,缺點增加了調度器的實現難度.

Goroutine 是 Go 語言中併發的執行單位。Goroutine 底層是使用協程 (coroutine) 實現,coroutine 是一種運行在用戶態的用戶線程(參考操作系統原理:內核態,用戶態)它可以由語言和框架層 調度。Go 在語言層面實現了調度器,同時對網絡,IO 庫進行了封裝處理,屏蔽了操作系統層面的 複雜的細節,在語言層面提供統一的關鍵字支持。

三者與內核級線程的關係如下圖所示:

GO 語言之 Goroutine 原理解析

一個 Machine 會對應一個內核線程(K),同時會有一個 Processor 與它綁定。一個 Processor 連 接一個或者多個Goroutine。Processor有一個運行時的Goroutine(上圖中綠色的G),其它的 Goroutine 處於等待狀態。

Processor 的數量同時可以併發任務的數量,可通過 GOMAXPROCS 限制同時執行用戶級任務的操 作系統線程。GOMAXPROCS 值默認是 CPU 的可用核心數,但是其數量是可以指定的。在 go 語言 運行時環境,可以使用

<code>runtime.GOMAXPROCS(MaxProcs)/<code>

來指定 Processor 數量。

  • 當一個 Goroutine 創建被創建時,Goroutine 對象被壓入 Processor 的本地隊列或者 Go 運行時 全局 Goroutine 隊列。
  • Processor 喚醒一個 Machine,如果 Machine 的 waiting 隊列沒有等待被 喚醒的 Machine, 則創建一個(只要不超過 Machine 的最大值,10000),Processor 獲取到 Machine 後,與 此 Machine 綁定,並執行此 Goroutine
<code>funcschedinit(){
//設置最大的M數量
sched.maxmcount =10000
}/<code>
  • Machine 執行過程中,隨時會發生上下文切換。當發生上下文切換時,需要對執行現場進行 保護,以便下次被調度執行時進行現場恢復。Go 調度器中 Machine 的棧保存在 Goroutine 對 象上,只需要將 Machine 所需要的寄存器(堆棧指針、程序計數器等)保存到 Goroutine 對象上 即可。
  • 如果此時 Goroutine 任務還沒有執行完,Machine 可以將 Goroutine 重新壓入 Processor 的隊 列,等待下一次被調度執行。
  • 如果執行過程遇到阻塞並阻塞超時,Machine 會與 Processor 分離,並等待阻塞結束。此時 Processor 可以繼續喚醒 Machine 執行其它的 Goroutine,當阻塞結束時,Machine 會嘗試” 偷取”一個 Processor,如果失敗,這個 Goroutine 會被加入到全局隊列中,然後 Machine 將 自己轉入 Waiting 隊列,等待被再次喚醒。

51Reboot 2.13號分享《Goroutine 原理解析》

內容:

1、goroutine 原理解析

  • GO 併發模式
  • GO 併發模型
  • GO 調度器 GMP

2、channel 原理 channel 數據結構

  • channel 實現方式

地址:https://ke.qq.com/course/860855?taid=5461862666543799&tuin=31589b0e


分享到:


相關文章: