Go語言實現一個簡單生產者消費者模型
簡介:介紹生產者消費者模型,及go簡單實現的demo。
一、生產者消費者模型
生產者消費者模型:某個模塊(函數等〉負責產生數據,這些數據由另一個模塊來負責處理(此處的模塊是廣義的,可以是類、函數、協程、線程、進程等)。產生數據的模塊,就形象地稱為生產者;而處理數據的模塊,就稱為消費者。
單單抽象出生產者和消費者,還夠不上是生產者消費者模型。該模式還需要有一個緩沖區處於生產者和消費者之間,作為一個中介。生產者把數據放入緩沖區,而消費者從緩沖區取出數據。大概的結構如下圖。
假設你要寄一件快遞,大致過程如下。.
1.把快遞封好——相當於生產者制造數據。
2.把快遞交給快遞中心——相當於生產者把數據放入緩沖區。
3.郵遞員把快遞從快遞中心取出——相當於消費者把數據取出緩沖區。
這麼看,有瞭緩沖區就有瞭以下好處:
解耦:降低消費者和生產者之間的耦合度。有瞭快遞中心,就不必直接把快遞交給郵寄員,郵寄快遞的人不對郵寄員產生任何依賴,如果某一個天郵寄員換人瞭,對於郵寄快遞的人也沒有影響。假設生產者和消費者分別是兩個類。如果讓生產者直接調用消費者的某個方法,那麼生產者對於消費者就會產生依賴(也就是耦合)。將來如果消費者的代碼發生變化,可能會真接影響到生產者。而如果兩者都依賴於某個緩沖區,兩者之間不直接依賴,耦合度也就相應降低瞭。
並發:生產者消費者數量不對等,依然能夠保持正常通信。由於函數調用是同步的(或者叫阻塞的),在消費者的方法沒有返回之前,生產者隻好一直等在那邊。萬一消費者處理數據很慢,生產者隻能等著浪費時間。使用瞭生產者消費者模式之後,生產者和消費者可以是兩個獨立的並發主體。生產者把制造出來的數據往緩沖區一丟,就可以再去生產下一個數據。基本上不用依賴消費者的處理速度。郵寄快遞的人直接把快遞扔個快遞中心之後就不用管瞭。
緩存:生產者消費者速度不匹配,暫存數據。如果郵寄快遞的人一次要郵寄多個快遞,那麼郵寄員無法郵寄,就可以把其他的快遞暫存在快遞中心。也就是生產者短時間內生產數據過快,消費者來不及消費,未處理的數據可以暫時存在緩沖區中。
二、Go語言實現
單向channel最典型的應用是“生產者消費者模型”。channel又分為有緩沖和無緩沖channel。channel中參數傳遞的時候,是作為引用傳遞。
1、無緩沖channel
示例代碼一實現如下
package main import "fmt" func producer(out chan <- int) { for i:=0; i<10; i++{ data := i*i fmt.Println("生產者生產數據:", data) out <- data // 緩沖區寫入數據 } close(out) //寫完關閉管道 } func consumer(in <- chan int){ // 同樣讀取管道 //for{ // val, ok := <- in // if ok { // fmt.Println("消費者拿到數據:", data) // }else{ // fmt.Println("無數據") // break // } //} // 無需同步機制,先做後做 // 沒有數據就阻塞等 for data := range in { fmt.Println("消費者得到數據:", data) } } func main(){ // 傳參的時候顯式類型像隱式類型轉換,雙向管道向單向管道轉換 ch := make(chan int) //無緩沖channel go producer(ch) // 子go程作為生產者 consumer(ch) // 主go程作為消費者 }
這裡使用無緩沖channel,生產者生產一次數據放入channel,然後消費者從channel讀取數據,如果沒有隻能等待,也就是阻塞,直到管道被關閉。所以宏觀是生產者消費者同步執行。
另外:這裡是隻而外開辟一個go程執行生產者,主go程執行消費者,如果也是用一個新的go程執行消費者,就需要阻塞main函數中的go程,否則不等待消費者和生產者執行完畢,主go程退出,程序直接結束,如示例代碼三。
生產者每一次生產,消費者也隻能拿到一次數據,緩沖區作用不大。結果如下:
2、有緩沖channel
示例代碼二如下
package main import "fmt" func producer(out chan <- int) { for i:=0; i<10; i++{ data := i*i fmt.Println("生產者生產數據:", data) out <- data // 緩沖區寫入數據 } close(out) //寫完關閉管道 } func consumer(in <- chan int){ // 無需同步機制,先做後做 // 沒有數據就阻塞等 for data := range in { fmt.Println("消費者得到數據:", data) } } func main(){ // 傳參的時候顯式類型像隱式類型轉換,雙向管道向單向管道轉換 ch := make(chan int, 5) // 添加緩沖區,5 go producer(ch) // 子go程作為生產者 consumer(ch) // 主go程作為消費者 }
有緩沖channel,隻修改ch := make(chan int, 5) // 添加緩沖
一句,隻要緩沖區不滿,生產者可以持續向緩沖區channel放入數據,隻要緩沖區不為空,消費者可以持續從channel讀取數據。就有瞭異步,並發的特性。
結果如下:
這裡之所以終端生產者連續打印瞭大於緩沖區容量的數據,是因為終端打印屬於系統調用也是有延遲的,IO操作的時候,生產者同時向管道寫入,請求打印,管道的寫入讀取與終端輸出打印速度不匹配。
三、實際應用
實際應用中,同時訪問同一個公共區域,同時進行不同的操作。都可以劃分為生產者消費者模型,比如訂單系統。
很多用戶的訂單下達之後,放入緩沖區或者隊列中,然後系統從緩沖區中去讀來真正處理。系統不必開辟多個線程來對應處理多個訂單,減少系統並發的負擔。通過生產者消費者模式,將訂單系統與倉庫管理系統隔離開,且用戶可以隨時下單(生產數據)。如果訂單系統直接調用倉庫系統,那麼用戶單擊下訂單按鈕後,要等到倉庫系統的結果返回。這樣速度會很慢。
也就是:用戶變成瞭生產者,處理訂單管理系統變成瞭消費者。
代碼示例三如下
package main import ( "fmt" "time" ) // 模擬訂單對象 type OrderInfo struct { id int } // 生產訂單--生產者 func producerOrder(out chan <- OrderInfo) { // 業務生成訂單 for i:=0; i<10; i++{ order := OrderInfo{id: i+1} fmt.Println("生成訂單,訂單ID為:", order.id) out <- order // 寫入channel } // 如果不關閉,消費者就會一直阻塞,等待讀 close(out) // 訂單生成完畢,關閉channel } // 處理訂單--消費者 func consumerOrder(in <- chan OrderInfo) { // 從channel讀取訂單,並處理 for order := range in{ fmt.Println("讀取訂單,訂單ID為:", order.id) } } func main() { ch := make(chan OrderInfo, 5) go producerOrder(ch) go consumerOrder(ch) time.Sleep(time.Second * 2) }
這裡如上面邏輯類似,不同的是用一個,OrderInfo結構體模擬訂單作為業務處理對象。主線程使用time.Sleep(time.Second * 2)
阻塞,否則,程序立即停止。
結果如下:
到此這篇關於Go語言實現一個簡單生產者消費者模型的文章就介紹到這瞭,更多相關Go語言生產者消費者 內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!