Go語言實現生產者-消費者問題的多種方法

Go語言實現生產者-消費者問題的多種方法

生產者-消費者問題是并發編程中的經典問題,涉及多個生產者生成數據,多個消費者消費數據,二者通過緩沖區(隊列)進行協調,保證數據的正確傳遞和同步。本文將從簡單到復雜,使用不同的 Go 語言并發原語實現生產者-消費者模型,并詳細介紹所用知識點。


目錄

  1. 方法一:使用無緩沖 Channel(同步通信)
  2. 方法二:使用帶緩沖 Channel(異步通信)
  3. 方法三:使用 sync.Mutex + 條件變量 sync.Cond 實現緩沖區
  4. 方法四:使用 Channel + select 實現多路復用和超時控制

方法一:使用無緩沖 Channel(同步通信)

知識點

  • 無緩沖 Channel:發送和接收必須同時準備好,適合嚴格同步的場景。
  • Goroutine:輕量級線程,使用 go 關鍵字啟動。
  • sync.WaitGroup:等待所有 goroutine 完成。

代碼示例

package mainimport ("fmt""sync""time"
)func producer(id int, ch chan<- int, wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 3; i++ {item := id*100 + ifmt.Printf("生產者 %d 生產了產品 %d\n", id, item)ch <- item                         // 發送數據,阻塞直到有消費者接收time.Sleep(100 * time.Millisecond) // 模擬生產時間}
}func consumer(id int, ch <-chan int, wg *sync.WaitGroup) {defer wg.Done()for item := range ch {fmt.Printf("消費者 %d 消費了產品 %d\n", id, item)time.Sleep(150 * time.Millisecond) // 模擬消費時間}
}func main() {ch := make(chan int) // 無緩沖 channelvar wg sync.WaitGroup// 啟動生產者for i := 1; i <= 2; i++ {wg.Add(1)go producer(i, ch, &wg)}// 啟動消費者for i := 1; i <= 2; i++ {wg.Add(1)go consumer(i, ch, &wg)}// 等待生產者完成wg.Wait()// 關閉 channel,通知消費者結束close(ch)// 由于消費者在 range 中消費,關閉后會退出// 這里主 goroutine 退出,程序結束
}

說明

  • 生產者發送數據時會阻塞,直到消費者接收,保證同步。
  • 適合生產和消費速度相近的場景。
  • 關閉 channel 后,消費者會自動退出。

方法二:使用帶緩沖 Channel(異步通信)

知識點

  • 帶緩沖 Channel:允許生產者先發送一定數量數據,消費者稍后接收,提升并發效率。
  • 生產者和消費者速度不匹配時,緩沖區能暫存數據,減少阻塞。

代碼示例

package mainimport ("fmt""math/rand""sync""time"
)func producer(id int, ch chan<- int, wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 5; i++ {item := id*100 + ifmt.Printf("生產者 %d 生產了產品 %d\n", id, item)ch <- itemtime.Sleep(time.Duration(rand.Intn(300)) * time.Millisecond)}
}func consumer(id int, ch <-chan int, wg *sync.WaitGroup) {defer wg.Done()for item := range ch {fmt.Printf("消費者 %d 消費了產品 %d\n", id, item)time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)}
}func main() {rand.Seed(time.Now().UnixNano())ch := make(chan int, 3) // 帶緩沖 channel,緩沖區大小為3var wgProducers sync.WaitGroupvar wgConsumers sync.WaitGroup// 啟動生產者for i := 1; i <= 3; i++ {wgProducers.Add(1)go producer(i, ch, &wgProducers)}// 啟動消費者for i := 1; i <= 2; i++ {wgConsumers.Add(1)go consumer(i, ch, &wgConsumers)}// 等待所有生產者完成wgProducers.Wait()// 關閉 channel,通知消費者沒有更多數據close(ch)// 等待所有消費者完成wgConsumers.Wait()fmt.Println("所有生產者和消費者已完成工作,程序結束")
}

說明

  • 生產者可以先發送數據到緩沖區,不必等待消費者立即接收。
  • 緩沖區大小影響生產者和消費者的阻塞情況。
  • 關閉 channel 后,消費者會自動退出。

方法三:使用 sync.Mutex + sync.Cond 實現緩沖區(手動實現隊列)

知識點

  • sync.Mutex:互斥鎖,保護共享資源。
  • sync.Cond:條件變量,支持等待和通知機制。
  • 手動實現緩沖區:用切片模擬隊列,生產者和消費者通過條件變量協調。

代碼示例

package mainimport ("fmt""sync""time"
)type Buffer struct {items    []intsize     intlock     sync.MutexnotEmpty *sync.CondnotFull  *sync.Cond
}func NewBuffer(size int) *Buffer {b := &Buffer{items: make([]int, 0, size),size:  size,}b.notEmpty = sync.NewCond(&b.lock)b.notFull = sync.NewCond(&b.lock)return b
}func (b *Buffer) Put(item int) {b.lock.Lock()defer b.lock.Unlock()// 如果緩沖區滿,等待 notFull 信號for len(b.items) == b.size {b.notFull.Wait()}b.items = append(b.items, item)fmt.Printf("生產了產品 %d,緩沖區大小: %d\n", item, len(b.items))// 通知消費者緩沖區非空b.notEmpty.Signal()
}func (b *Buffer) Get() int {b.lock.Lock()defer b.lock.Unlock()// 如果緩沖區空,等待 notEmpty 信號for len(b.items) == 0 {b.notEmpty.Wait()}item := b.items[0]b.items = b.items[1:]fmt.Printf("消費了產品 %d,緩沖區大小: %d\n", item, len(b.items))// 通知生產者緩沖區非滿b.notFull.Signal()return item
}func producer(id int, b *Buffer, count int, wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < count; i++ {item := id*100 + ib.Put(item)time.Sleep(100 * time.Millisecond)}
}func consumer(id int, b *Buffer, wg *sync.WaitGroup, done <-chan struct{}) {defer wg.Done()for {select {case <-done:returndefault:item := b.Get()time.Sleep(150 * time.Millisecond)fmt.Printf("消費者 %d 處理了產品 %d\n", id, item)}}
}func main() {bufferSize := 5b := NewBuffer(bufferSize)var wgProducers sync.WaitGroupvar wgConsumers sync.WaitGroupdone := make(chan struct{})// 啟動生產者numProducers := 2produceCount := 10for i := 1; i <= numProducers; i++ {wgProducers.Add(1)go producer(i, b, produceCount, &wgProducers)}// 啟動消費者numConsumers := 3for i := 1; i <= numConsumers; i++ {wgConsumers.Add(1)go consumer(i, b, &wgConsumers, done)}// 等待生產者完成wgProducers.Wait()// 生產結束,等待緩沖區清空for {b.lock.Lock()empty := len(b.items) == 0b.lock.Unlock()if empty {break}time.Sleep(100 * time.Millisecond)}// 通知消費者退出close(done)// 等待消費者退出wgConsumers.Wait()fmt.Println("所有生產者和消費者已完成工作,程序結束")
}

說明

  • 手動實現緩沖區,生產者和消費者通過條件變量等待和通知。
  • 適合需要自定義緩沖區行為的場景。
  • 需要額外處理消費者退出邏輯。

方法四:使用 Channel + select 實現多路復用和超時控制

知識點

  • select:Go 語言中用于監聽多個 channel 的操作,支持超時和默認分支。
  • 超時控制:防止 goroutine 永久阻塞。
  • 多路復用:同時監聽多個事件。

代碼示例

package mainimport ("fmt""math/rand""time"
)func producer(id int, ch chan<- int, done <-chan struct{}) {for i := 0; i < 10; i++ {item := id*100 + iselect {case ch <- item:fmt.Printf("生產者 %d 生產了產品 %d\n", id, item)case <-done:fmt.Printf("生產者 %d 收到退出信號\n", id)return}time.Sleep(time.Duration(rand.Intn(300)) * time.Millisecond)}
}func consumer(id int, ch <-chan int, done <-chan struct{}) {for {select {case item, ok := <-ch:if !ok {fmt.Printf("消費者 %d 發現通道關閉,退出\n", id)return}fmt.Printf("消費者 %d 消費了產品 %d\n", id, item)time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)case <-done:fmt.Printf("消費者 %d 收到退出信號\n", id)returncase <-time.After(2 * time.Second):fmt.Printf("消費者 %d 超時退出\n", id)return}}
}func main() {rand.Seed(time.Now().UnixNano())ch := make(chan int, 5)done := make(chan struct{})// 啟動生產者for i := 1; i <= 3; i++ {go producer(i, ch, done)}// 啟動消費者for i := 1; i <= 2; i++ {go consumer(i, ch, done)}// 運行一段時間后關閉生產者time.Sleep(5 * time.Second)close(done) // 通知所有 goroutine 退出// 關閉 channel,通知消費者沒有更多數據close(ch)// 主 goroutine 等待一段時間讓所有 goroutine 退出time.Sleep(3 * time.Second)fmt.Println("程序結束")
}

說明

  • 使用 select 監聽多個 channel,支持超時和退出信號。
  • 生產者和消費者都能響應退出通知,優雅結束。
  • 適合復雜場景下的生產者-消費者模型。

總結

方法復雜度關鍵知識點適用場景
方法一簡單無緩沖 channel,阻塞同步生產消費速度相近,簡單同步
方法二中等帶緩沖 channel,異步通信生產消費速度不匹配,提升效率
方法三較復雜sync.Mutex + sync.Cond,手動緩沖區需要自定義緩沖區行為,復雜同步
方法四復雜select 多路復用,超時控制,退出通知復雜場景,需多事件監聽和優雅退出

Go 語言提供了豐富的并發原語,能夠靈活實現生產者-消費者模型。根據實際需求和復雜度選擇合適的方法,能讓程序更高效、健壯。


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/905762.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/905762.shtml
英文地址,請注明出處:http://en.pswp.cn/news/905762.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Opencv】canny邊緣檢測提取中心坐標

采用opencv 對圖像中的小球通過canny邊緣檢測的方式進行提取坐標 本文介紹了如何使用OpenCV對圖像中的小球進行Canny邊緣檢測&#xff0c;并通過Zernike矩進行亞像素邊緣檢測&#xff0c;最終擬合橢圓以獲取小球的精確坐標。首先&#xff0c;圖像被轉換為灰度圖并進行高斯平滑…

藍橋杯12屆國B 123

題目描述 小藍發現了一個有趣的數列&#xff0c;這個數列的前幾項如下&#xff1a; 1,1,2,1,2,3,1,2,3,4,? 小藍發現&#xff0c;這個數列前 1 項是整數 1&#xff0c;接下來 2 項是整數 1 至 2&#xff0c;接下來 3 項是整數 1 至 3&#xff0c;接下來 4 項是整數 1 至 4&…

鴻蒙OSUniApp 制作動態加載的瀑布流布局#三方框架 #Uniapp

使用 UniApp 制作動態加載的瀑布流布局 前言 最近在開發一個小程序項目時&#xff0c;遇到了需要實現瀑布流布局的需求。眾所周知&#xff0c;瀑布流布局在展示不規則尺寸內容&#xff08;如圖片、商品卡片等&#xff09;時非常美觀和實用。但在實際開發過程中&#xff0c;我…

ThinkStation圖形工作站進入BIOS方法

首先視頻線需要接在獨立顯卡上&#xff0c;重新開機&#xff0c;持續按F1&#xff0c;或者顯示器出來lenovo的logo的時候按F1&#xff0c;這樣就進到bios里了。聯*想*坑&#xff0c;戴爾貴。靠。

【源碼級開發】Qwen3接入MCP,企業級智能體開發實戰!

Qwen3接入MCP智能體開發實戰&#xff08;上&#xff09; 一、MCP技術與Qwen3原生MCP能力介紹 1.智能體開發核心技術—MCP 1.1 Function calling技術回顧 如何快速開發一款智能體應用&#xff0c;最關鍵的技術難點就在于如何讓大模型高效穩定的接入一些外部工具。而在MCP技術…

Linux下載與安裝

一、YUM 1.1 什么是YUM 在CentOS系統中&#xff0c;軟件管理方式通常有三種方式&#xff1a;rpm安裝、yum安裝以及編譯&#xff08;源碼&#xff09;安裝。 編譯安裝&#xff0c;從過程上來講比較麻煩&#xff0c;包需要用戶自行下載&#xff0c;下載的是源碼包&#xff0c;需…

PostgreSQL中的全頁寫

一、概述 在PGSQL數據庫中&#xff0c;默認的頁面大小為8KB&#xff0c;但是磁盤buffer的大小為4KB&#xff0c;扇區大小為512B。這就導致在操作系統的角度看數據庫的寫操作&#xff0c;其實并不是一種原子操作。如果操作系統發生了系統級別的故障&#xff0c;此時正好操作系統…

WEB安全--Java安全--shiro550反序列化漏洞

一、前言 什么是shiro&#xff1f; shiro是一個Apache的Java安全框架 它的作用是什么&#xff1f; Apache Shiro 是一個強大且靈活的 Java 安全框架&#xff0c;用于處理身份驗證、授權、密碼管理以及會話管理等功能 二、shiro550反序列化原理 1、用戶首次登錄并勾選記住密碼…

2024 睿抗機器人開發者大賽CAIP-編程技能賽-專科組(國賽)解題報告 | 珂學家

前言 題解 2024 睿抗機器人開發者大賽CAIP-編程技能賽-專科組&#xff08;國賽&#xff09;&#xff0c;陳越姐姐出題。 國賽比省賽&#xff0c;難度增強了不少&#xff0c;題目就剩下4個題了。 涉及堆棧&#xff0c;hash表&#xff0c;優先隊列等高階數據結構的使用&#x…

15 C 語言字符類型詳解:轉義字符、格式化輸出、字符類型本質、ASCII 碼編程實戰、最值宏匯總

1 字符類型概述 在 C 語言中&#xff0c;字符類型 char 用于表示單個字符&#xff0c;例如一個數字、一個字母或一個符號。 char 類型的字面量是用單引號括起來的單個字符&#xff0c;例如 A、5 或 #。 當需要表示多個字符組成的序列時&#xff0c;就涉及到了字符串。在 C 語言…

操作系統-鎖/內存/中斷/IO

文章目錄 鎖自旋鎖互斥鎖悲觀鎖和樂觀鎖 內存管理物理/虛擬內存頁表段表虛擬內存布局寫時復制copy on writebrk&#xff0c;mmap頁面置換算法 中斷中斷分類中斷流程 網絡I/OI/O模型服務器處理并發請求 鎖 自旋鎖 自旋鎖是一種基于忙等待&#xff08;Busy-Waiting&#xff09;…

割點與其例題

割點 定義&#xff1a; 若一個點在圖中被去掉后&#xff0c;圖的連通塊個數增加&#xff0c;那么這個點就被稱為“割點”。如下圖所示紅點。 定義說白了就是若去掉一個點&#xff0c;圖被“斷開”的點稱為割點。 樸素算法&#xff1a; 枚舉每個點 u。遍歷圖&#xff0c;如果…

圖卷積神經網絡(Graph Convolutional Network, GCN)

最近看論文看到了圖卷積神經網絡的內容&#xff0c;之前整理過圖神經網絡的內容&#xff0c;這里再補充一下&#xff0c;方便以后查閱。 圖卷積神經網絡&#xff08;Graph Convolutional Network, GCN&#xff09; 圖卷積神經網絡1. 什么是圖卷積神經網絡&#xff08;GCN&#…

安裝win11硬盤分區MBR還是GPT_裝win11系統分區及安裝教程

最近有網友問我,裝win11系統分區有什么要求裝win11系統硬盤分區用mbr還是GPT&#xff1f;我們知道現在的引導模式有uefi和legacy兩種引導模式&#xff0c;如果采用的是uefi引導模式&#xff0c;分區類型對應的就是gpt分區(guid)&#xff0c;如果引導模式采用的是legacy&#xf…

服務培訓QDA 的安裝調試方法,硬件模塊的講解和軟件控制臺使用及系統測試

#服務培訓##質譜儀##軟件控制##硬件模塊# 以下是關于Waters QDa單桿液質質譜儀的安裝調試、硬件模塊講解以及軟件控制臺使用培訓的相關內容&#xff1a; 安裝調試 場地準備&#xff1a;用戶需要提前準備好實驗室&#xff0c;確保實驗室環境符合儀器的要求&#xff0c;如溫度、…

在K8S集群中部署EFK日志收集

目錄 引言環境準備安裝自定義資源部署ElasticsearchMaster 節點與 Data 節點的區別生產優化建議安裝好以后測試ES是否正常部署Fluentd測試filebeat是否正常推送日志部署Kibana獲取賬號密碼&#xff0c;賬號是&#xff1a;elastic集群測試 引言 系統版本為 Centos7.9內核版本為…

polarctf-web-[rce1]

考點&#xff1a; (1)RCE(exec函數) (2)空格繞過 (3)執行函數(exec函數) (4)閉合(ping命令閉合) 題目來源&#xff1a;Polarctf-web-[rce1] 解題&#xff1a; 這段代碼實現了一個簡單的 Ping 測試工具&#xff0c;用戶可以通過表單提交一個 IP 地址&#xff0c;服務器會執…

【串流VR手勢】Pico 4 Ultra Enterprise 在 SteamVR 企業串流中無法識別手勢的問題排查與解決過程(Pico4UE串流手勢問題)

寫在前面的話 此前&#xff08;用Pico 4U&#xff09;接入了MRTK3&#xff0c;現項目落地需要部署&#xff0c;發現串流場景中&#xff0c;Pico4UE的企業串流無法正常識別手勢。&#xff08;一體機方式部署使用無問題&#xff09; 花了半小時解決&#xff0c;怕忘&#xff0c;…

ES(Elasticsearch)的應用與代碼示例

Elasticsearch應用與代碼示例技術文章大綱 一、引言 Elasticsearch在現代化應用中的核心作用典型應用場景分析&#xff08;日志分析/全文檢索/數據聚合&#xff09; 二、環境準備(前提條件) Elasticsearch 8.x集群部署要點IK中文分詞插件配置指南Ingest Attachment插件安裝…

臨床決策支持系統的提示工程優化路徑深度解析

引言 隨著人工智能技術在醫療領域的迅猛發展,臨床決策支持系統(CDSS)正經歷從傳統規則引擎向智能提示工程的范式轉變。在這一背景下,如何構建既符合循證醫學原則又能適應個體化醫療需求的CDSS成為醫學人工智能領域的核心挑戰。本報告深入剖析了臨床決策支持系統中提示工程的…