Go語言交替打印問題及多種實現方法

Go語言交替打印問題及多種實現方法

在并發編程中,多個線程(或 goroutine)交替執行任務是一個經典問題。本文將以 Go 語言為例,介紹如何實現多個 goroutine 交替打印數字的功能,并展示幾種不同的實現方法。


Go 語言相關知識點

1. Goroutine

Goroutine 是 Go 語言的輕量級線程,使用 go 關鍵字啟動。它們由 Go 運行時調度,能夠高效地并發執行任務。

2. Channel

Channel 是 Go 語言中用于 goroutine 之間通信的管道。通過 channel,goroutine 可以發送和接收數據,實現同步和通信。

  • chan T 表示傳輸類型為 T 的 channel。
  • 發送數據:ch <- value
  • 接收數據:value := <- ch

3. sync.Mutex

互斥鎖,用于保護共享資源,防止多個 goroutine 同時訪問導致數據競爭。

4. sync.WaitGroup

用于等待一組 goroutine 完成。通過 Add 設置計數,Done 表示完成,Wait 阻塞直到計數歸零。


需求描述

  • n 個 goroutine(線程),編號從 1 到 n。
  • 這 n 個 goroutine 交替打印數字,從 1 打印到 max
  • 例如,3 個 goroutine,打印 1,2,3,4,…30,線程1打印1,線程2打印2,線程3打印3,線程1打印4,依次循環。

方法一:使用多個 Channel 輪流通知(基于題主代碼)

思路:

  • 創建 n 個 channel,分別對應每個 goroutine。
  • 每個 goroutine 等待自己的 channel 收到信號后打印數字,然后通知下一個 goroutine。
  • 使用互斥鎖保護共享計數器。
  • 使用一個 done channel 通知所有 goroutine 退出。
package mainimport ("fmt""sync"
)func main() {const max = 30const n = 3 // goroutine 數量channels := make([]chan bool, n)for i := 0; i < n; i++ {channels[i] = make(chan bool)}var wg sync.WaitGroupwg.Add(n)counter := 1var mu sync.Mutexdone := make(chan struct{})for i := 0; i < n; i++ {go func(id int) {defer wg.Done()for {select {case <-done:returncase _, ok := <-channels[id]:if !ok {return}mu.Lock()if counter > max {mu.Unlock()close(done)return}fmt.Printf("線程 %d 打印 %d\n", id+1, counter)counter++mu.Unlock()channels[(id+1)%n] <- true}}}(i)}// 啟動第一個 goroutinechannels[0] <- truewg.Wait()for i := 0; i < n; i++ {close(channels[i])}fmt.Println("打印結束")
}

方法二:使用單個 Channel 和 goroutine ID 控制

思路:

  • 使用一個 channel 傳遞當前應該打印的 goroutine ID。
  • 每個 goroutine 監聽 channel,只有當收到的 ID 與自己相同時才打印數字。
  • 打印后將下一個 goroutine 的 ID 發送回 channel。
package mainimport ("fmt""sync"
)func main() {const max = 30const n = 3ch := make(chan int)var wg sync.WaitGroupwg.Add(n)counter := 1var mu sync.Mutexfor i := 0; i < n; i++ {go func(id int) {defer wg.Done()for {curID := <-chif curID != id {// 不是自己的輪次,放回去ch <- curIDcontinue}mu.Lock()if counter > max {mu.Unlock()// 結束所有 goroutine// 發送特殊值 -1 表示結束ch <- -1return}fmt.Printf("線程 %d 打印 %d\n", id+1, counter)counter++mu.Unlock()// 發送下一個 goroutine 的 IDch <- (id + 1) % n}}(i)}// 啟動第一個 goroutinech <- 0wg.Wait()fmt.Println("打印結束")
}

方法三:使用 sync.Cond 條件變量

思路:

  • 使用一個共享變量 counterturn 表示當前輪到哪個 goroutine 打印。
  • 使用 sync.Cond 來等待和通知 goroutine。
  • 每個 goroutine 等待條件滿足(輪到自己),打印數字后更新 turn 并通知其他 goroutine。
package mainimport ("fmt""sync"
)func main() {const max = 30const n = 3var mu sync.Mutexcond := sync.NewCond(&mu)counter := 1turn := 0var wg sync.WaitGroupwg.Add(n)for i := 0; i < n; i++ {go func(id int) {defer wg.Done()for {mu.Lock()for turn != id && counter <= max {cond.Wait()}if counter > max {mu.Unlock()cond.Broadcast()return}fmt.Printf("線程 %d 打印 %d\n", id+1, counter)counter++turn = (turn + 1) % nmu.Unlock()cond.Broadcast()}}(i)}wg.Wait()fmt.Println("打印結束")
}

方法四:使用 Channel + select + 超時退出

思路:

  • 使用一個 channel 傳遞打印任務。
  • 每個 goroutine 監聽 channel,只有當任務分配給自己時打印。
  • 使用超時機制防止死鎖。
package mainimport ("fmt""time"
)func main() {const max = 30const n = 3type task struct {id      intcounter int}ch := make(chan task)for i := 0; i < n; i++ {go func(id int) {for {select {case t := <-ch:if t.id != id {// 不是自己的任務,放回去ch <- tcontinue}if t.counter > max {// 結束信號,放回去讓其他 goroutine 退出ch <- treturn}fmt.Printf("線程 %d 打印 %d\n", id+1, t.counter)time.Sleep(100 * time.Millisecond) // 模擬工作ch <- task{id: (id + 1) % n, counter: t.counter + 1}case <-time.After(2 * time.Second):// 超時退出return}}}(i)}// 啟動第一個任務ch <- task{id: 0, counter: 1}// 等待足夠時間讓所有打印完成time.Sleep(5 * time.Second)fmt.Println("打印結束")
}

總結

  • Go 語言提供了多種并發原語,能夠靈活實現線程間的協作。
  • Channel 是 goroutine 通信的核心,適合用于事件通知和數據傳遞。
  • sync.Mutex 和 sync.Cond 適合保護共享資源和實現復雜的同步邏輯。
  • 選擇哪種方法取決于具體需求和代碼風格。

通過以上幾種方法,你可以根據實際場景選擇合適的實現方式,實現多個 goroutine 交替打印數字的功能。

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

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

相關文章

支持藍牙5.0和2.4G私有協議芯片-PHY6222

PHY6222QC-W04C 是一款適用于藍牙低功耗&#xff08;BLE&#xff09;5.2 應用的片上系統&#xff08;SoC&#xff09;。它搭載 ARM Cortex?-M0 32 位處理器&#xff0c;配備 64KB SRAM、512K Flash、96KB ROM、256 bit efuse &#xff0c;以及超低功耗、高性能的多模式射頻模塊…

git相關配置

git相關配置 歡迎使用Markdown編輯器修改Git默認編輯器為vimgit配置默認用戶名和密碼&#xff1a; 歡迎使用Markdown編輯器 修改Git默認編輯器為vim #方法1&#xff1a;直接執行 git config --global core.editor vim#方法2&#xff1a;修改git的配置文件.git/config文件&am…

C語言實現INI配置文件讀取和寫入

一.INI文件介紹 INI配置文件是一種簡單的文本文件&#xff0c;用于存儲配置信息&#xff0c;通常由一個或多個節&#xff08;section&#xff09;組成&#xff0c;每個節包含多個鍵值對&#xff08;Key-Value&#xff09;格式。INI文件易于閱讀和編輯&#xff0c;廣泛應用于多…

Vue 3 打開 el-dialog 時使 el-input 獲取焦點

運行代碼&#xff1a;https://andi.cn/page/622178.html 效果&#xff1a;

【程序員AI入門:模型】19.開源模型工程化全攻略:從選型部署到高效集成,LangChain與One-API雙劍合璧

一、模型選型與驗證&#xff1a;精準匹配業務需求 &#xff08;一&#xff09;多維度評估體系 通過量化指標權重實現科學選型&#xff0c;示例代碼計算模型綜合得分&#xff1a; # 評估指標權重與模型得分 requirements {"accuracy": 0.4, "latency": …

卡頓檢測與 Choreographer 原理

一、卡頓檢測的原理 卡頓的本質是主線程&#xff08;UI 線程&#xff09;未能及時完成某幀的渲染任務&#xff08;超過 16.6ms&#xff0c;以 60Hz 屏幕為例&#xff09;&#xff0c;導致丟幀&#xff08;Frame Drop&#xff09;。檢測卡頓的核心思路是監控主線程任務的執行時…

物聯網僵尸網絡防御:從設備認證到流量染色

一、IoT設備的安全困境 典型物聯網設備存在硬編碼密鑰問題&#xff1a; // 固件中的危險代碼示例 const char* DEFAULT_KEY "A1B2-C3D4-E5F6"; // 廠商預設密鑰 void connect_server() {authenticate(DEFAULT_KEY); // 密鑰從未更新 }此類漏洞導致某智能家居平臺…

二叉樹子樹判斷:從遞歸到迭代的全方位解析

一、題目解析 題目描述 給定兩棵二叉樹root和subRoot&#xff0c;判斷root中是否存在一棵子樹&#xff0c;其結構和節點值與subRoot完全相同。 示例說明 示例1&#xff1a; root [3,4,5,1,2]&#xff0c;subRoot [4,1,2] 返回true&#xff0c;因為root的左子樹與subRoot完…

Springboot 異步場景 使用注解 @Async 及 自定義線程池分模塊使用

目錄 前言一、Springboot項目如何開啟異步&#xff1f;二、存在的問題三、自定義線程池四、自定義線程池使用五、阻塞隊列和拒絕策略 前言 當開發中遇到不影響主流程任務時&#xff0c;使用異步去處理。 如有以下場景&#xff1a; 1、業務需要生成一個季度的數據進行員工排名&…

【GNN筆記】Signed Graph Convolutional Network(12)【未完】

視頻鏈接&#xff1a;《圖神經網絡》 Signed Graph Convolutional Network 之前介紹的GNN模型主要集中在無符號的網絡&#xff08;或僅由正鏈接組成的圖&#xff09;上&#xff0c;符號 圖帶來的挑戰&#xff0c;主要集中在于 否定鏈接&#xff0c;與正鏈接相比&#xff0c;它不…

米勒電容補償的理解

米勒電容補償是使運放放大器穩定的重要手法&#xff0c;可以使兩級運放的兩個極點分離&#xff0c;從而可以得到更好的相位裕度。 Miller 電容補償的本質是增加一條通路流電流&#xff0c;流電流才是miller效應的本質。給定一個相同的輸入&#xff0c;Miller 電容吃掉的電流比…

CVE-2017-8046 漏洞深度分析

漏洞概述 CVE-2017-8046 是 Spring Data REST 框架中的一個高危遠程代碼執行漏洞&#xff0c;影響版本包括 Spring Data REST < 2.5.12、2.6.7、3.0 RC3 及關聯的 Spring Boot 和 Spring Data 舊版本。攻擊者通過構造包含惡意 SpEL&#xff08;Spring Expression Language&…

qt文本邊框設置

// 計算文本的大致尺寸 QFontMetrics fm(textEditor->font()); QRect textRect fm.boundingRect(textItem->toPlainText()); // 設置編輯框大小&#xff0c;增加一些邊距 const int margin 10; textEditor->setGeometry( center.x() - textRect.width()/2 - margin,…

Java 與 面向對象編程(OOP)

Java 是典型的純面向對象編程語言&#xff08;Pure Object-Oriented Language&#xff09;&#xff0c;其設計嚴格遵循面向對象&#xff08;OOP&#xff09;的核心原則。以下是具體分析&#xff1a; 1. Java 的面向對象核心特性 (1) 一切皆對象 Java 中幾乎所有的操作都圍繞…

導出導入Excel文件(詳解-基于EasyExcel)

前言&#xff1a; 近期由于工作的需要&#xff0c;根據需求需要導出導入Excel模板。于是自學了一下下&#xff0c;在此記錄并分享&#xff01;&#xff01; EasyExcel&#xff1a; 首先我要在這里非常感謝阿里的大佬們&#xff01;封裝這么好用的Excel相關的API&#xff0c;真…

python版本管理工具-pyenv輕松切換多個Python版本

在使用python環境開發時&#xff0c;相信肯定被使用版本所煩惱&#xff0c;在用第三方庫時依賴兼容的python版本不一樣&#xff0c;有沒有一個能同時安裝多個python并能自由切換的工具呢&#xff0c;那就是pyenv&#xff0c;讓你可以輕松切換多個Python 版本。 pyenv是什么 p…

Elasticsearch 索引副本數

作者&#xff1a;來自 Elastic Kofi Bartlett 解釋如何配置 number_of_replicas、它的影響以及最佳實踐。 更多閱讀&#xff1a;Elasticsearch 中的一些重要概念: cluster, node, index, document, shards 及 replica 想獲得 Elastic 認證&#xff1f;查看下一期 Elasticsearc…

AXI4總線協議 ------ AXI_LITE協議

一、AXI 相關知識介紹 https://download.csdn.net/download/mvpkuku/90841873 AXI_LITE 選出部分重點&#xff0c;詳細文檔見上面鏈接。 1.AXI4 協議類型 2.握手機制 二、AXI_LITE 協議的實現 1. AXI_LITE 通道及各通道端口功能介紹 2.實現思路及框架 2.1 總體框架 2.2 …

idea運行

各種小kips Linuxidea上傳 Linux 部署流程 1、先在idea打好jar包&#xff0c;clean之后install 2、在Linux目錄下&#xff0c;找到對應項目目錄&#xff0c;把原來的jar包放在bak文件夾里面 3、殺死上一次jar包的pid ps -ef|grep cliaidata.jar kill pid 4、再進行上傳新的jar…

FPGA: XILINX Kintex 7系列器件的架構

本文將詳細介紹Kintex-7系列FPGA器件的架構。以下內容將涵蓋Kintex-7的核心架構特性、主要組成部分以及關鍵技術&#xff0c;盡量全面且結構化&#xff0c;同時用簡潔的語言確保清晰易懂。 Kintex-7系列FPGA架構概述 Kintex-7是Xilinx 7系列FPGA中的中高端產品線&#xff0c;基…