首頁 > 軟體

範例剖析golang中的CSP並行模型

2022-05-25 22:01:13

1. 相關概念: 

使用者態:當一個程序在執行使用者自己的程式碼時處於使用者執行態(使用者態)

核心態:當一個程序因為系統呼叫陷入核心程式碼中執行時處於核心執行態(核心態),引入核心態防止使用者態的程式隨意的操作核心地址空間,具有一定的安全保護作用。這種保護模式是通過記憶體頁表操作等機制,保證程序間的地址空間不會相互衝突,一個程序的操作不會修改另一個程序地址空間中的資料。

使用者態與核心態之間的切換:當在系統中執行一個程式時,大部分時間都是執行在使用者態下的,在其需要作業系統幫助完成一些使用者態自己沒有特權和能力完成的操作時就會切換到核心態。有以下三種方式:

(1)系統呼叫(中斷)使用者態程序主動要求切換到核心態的一種方式。

(2)異常cpu執行時如果發生一些沒有預知的異常,會觸發當前程序切換到處理此異常的核心相關程序中。 

(3)外圍裝置的中斷使用者態程序主動要求切換到核心態的一種方式。

協程:又稱微執行緒,纖程。英文名Coroutine。Coroutine是一種執行在使用者態的使用者執行緒,類似於 greenthread。協程與執行緒都相互獨立,且有自己的上下文,不同之處在於,協程的切換由其自身控制,而執行緒的切換收到系統排程。

2. CSP (交談循序程式) 

CSP模型用來描述兩個獨立的並行實體通過共用的通訊channel管道進行通訊的並行模型。

golang借用了CSP模型的一些概念如:實體 process,通道 channel,為之實現並行進行了理論支援,實際上並沒有完全實現CSP模型的所有理論。process是在go語言上的表現就是goroutine,是實際上並行執行的實體,每個實體之間是通過channel通訊來實現資料共用。

3. channel:同步&傳遞訊息

channel是被單獨建立並且可以在程序之間傳遞,它的通訊模式類似於boss-worker模式,一個實體通過將訊息傳送到channel中,然後又監聽這個channel的實體處理,兩個實體之間是匿名的,實現原理上其實是一個阻塞的訊息佇列。

具體可以分為:有/無快取channel,唯讀channel,只寫channel,雙向channel

寫操作:chan <- value

讀操作:<- chan

// Create channel
// Unbuffered channel
umbuffer_chan := make(chan int)
// Buffered channel
// Buffer Size = 3
buffer_chan := make(chan int,3)
// Read-Only channel
read_channel := make(&lt;-chan int)
// Receive-Only channel
receive_channel := make(chan&lt;- int)

生產者-消費者Sample:

package main
import (
   "fmt" 
   "time"
)
// 生產者
func Producer (queue chan&lt;- int){
        for i:= 0; i &lt; 10; i++ {
                queue &lt;- i
        }
}
// 消費者
func Consumer( queue &lt;-chan int){
        for i :=0; i &lt; 10; i++{
                v := &lt;- queue
                fmt.Println("receive:", v)
        }
}
func main(){
        queue := make(chan int, 1)
        go Producer(queue)
        go Consumer(queue)
        time.Sleep(1e9) //讓Producer與Consumer完成
}

4. goroutine:實際並行執行的實體

在函數或者方法前面加上關鍵字go,就建立了並行執行的goroutine,eg:

go func (){
}
func Test(){
}
// ...
go Test()

範例程式碼:

package main  // 程式碼包宣告語句。
import (
   "fmt" //系統包用來輸出的
   "math/rand"
   "runtime"
   "sync"
   "time"
)
func main() {
   // 分配一個邏輯處理器給排程器使用
   runtime.GOMAXPROCS(1)
   // WaitGroup是一個計數號誌,用來記錄和維護執行的goroutine,如果當前的wg&gt;0,對應的exit方法就會阻塞
   var wg sync.WaitGroup
   // 計數加2表示要等待兩個goroutine
   wg.Add(2)
   fmt.Printf("Start Goroutines n", )
   // 宣告匿名函數,建立goroutine
   go func(){
      // 關鍵字defer會修改函數呼叫時機,在函數退出時呼叫Done來通知main函數工作已經完成
      defer wg.Done()
      for count:=0; count&lt;3; count++ {
         for char :='a'; char&lt;'a'+26 ; char++ {
            fmt.Printf("%c ", char)
         }
      }
   }()
   // 宣告匿名函數,建立goroutine
   go func() {
      // 函數退出時呼叫Done來通知main函數工作已經完成
      defer wg.Done()
      for count:=0; count&lt;3; count++ {
         for char :='A'; char&lt;'A'+26 ; char++ {
            fmt.Printf("%c ", char)
         }
      }
   }()
   fmt.Println("Waiting to finish!n", )
   // 等待結束
   wg.Wait()
   fmt.Println("nTerminate program! n", )
}

5. golang排程器

OS在物理處理器上排程執行緒來執行,而golang在邏輯處理器上排程goroutine來執行。每個邏輯處理器都分別繫結到單個作業系統執行緒。

如果建立一個goroutine並準備執行,這個goroutine就會被放到排程器的全域性執行佇列中。之後,排程器就會將佇列中的goroutine分配給一個邏輯處理器,並放到這個邏輯處理器對應的本地執行佇列中。本地執行佇列中的goroutine會一直等待,知道自己被分配到相應的邏輯處理器上執行。

eg:

其中:

M:Machine,一個M直接關聯了一個核心執行緒。

P:Processor,代表了M所需要的上下文環境,也就是處理使用者級程式碼邏輯的處理器。

G:Goroutine,本質上是一種輕量級的執行緒--協程。

MPG模型,三者關係的宏觀圖為:

Processor的作用:

當核心執行緒阻塞的時候,由於上下文的存在,我們能夠直接放開其他執行緒,繼續去執行未阻塞的執行緒,例子如下:

如果當前,G0由於I/O,系統呼叫進行了阻塞,這個時候M0就可以放開其他的執行緒:

M0和G0進行系統呼叫,等待返回值,上下文P以及routine佇列交由M1進行執行。當M0執行系統呼叫結束後,M0會嘗試去steal("偷")一個上下文,如果不成功,M0就把它的G0放到一個全域性的執行佇列中,然後將自己放到執行緒池或者轉入休眠狀態。

Global runqueue是各個上下文P在執行完自己的原生的goroutine runqueue後用來拉取新的goroutine的地方(steal working演演算法)。此外,P也會週期性的檢查Global runqueue上的goroutine,來防止全域性上的goroutine因為得不到執行而餓死。

以上就是範例剖析golang中的CSP並行模型的詳細內容,更多關於golang CSP並行模型的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com