Go語言中Goroutine的等待方式

更新於 2024/09/12閱讀時間約 9 分鐘


raw-image

👨‍💻簡介

昨天講到Goroutine的橋梁aka傳話筒 — Channel,那要怎麼知道對方有收到訊息,我的紙條有送到對方手上呢? 今天就是要來介紹幾種Goroutine的確定完成工作的幾種方式。

Goroutine 的等待方式

在Go語言中,等待Goroutine完成的方式有多種。

sync.WaitGroup

sync.WaitGroup是Go語言中常見的一種等待方式,它允許我們等待一組Goroutine完成。

package main

import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // 表示Goroutine完成工作,減少WaitGroup計數
fmt.Printf("Worker %d 正在工作\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // 增加WaitGroup數量
go worker(i, &wg) // 啟動Goroutine,並將WaitGroup的指針傳遞給它
}
wg.Wait() // 將主程式阻塞,直到所有Goroutines完成工作。
fmt.Println("所有工作完成")
}

在上面的範例中,我們先創建了一個sync.WaitGroup的變數wg,並使用for循環去執行我們的goroutine,每次執行前都先將等待goroutine的數量+1,總共等待三個Goroutine完成工作。 接著使用 wg.Wait()將main函數先暫停一下,等所有goroutine都完成工作,wg的計數器為0,在繼續執行程式。 在 worker函數裡,會接收 wg的pointer,並在開頭使用defer wg.Done()在程式執行結束就將等待的goroutine數量-1,來確保goroutine會完成

Channel

Channel是Go語言中用於通信的機制,也可以用於等待Goroutine完成。

package main

import (
"fmt"
)
func worker(id int, ch chan bool) {
fmt.Printf("Worker %d 正在工作\n", id)
ch <- true
}
func main() {
ch := make(chan bool)
for i := 1; i <= 3; i++ {
go worker(i, ch)
}
for i := 1; i <= 3; i++ {
<-ch
}
fmt.Println("所有工作完成")
}

Channel的特性就是有方向性,在這段程式碼中,創建了三個Goroutines,每個Goroutine在執行時都印出一條訊息,然後將一個布林值傳送到Channel中,最後等三個Channel都接收到操作完成後,印出 “所有工作完成”。

select

使用select語句可以等待多個Goroutine中的任何一個完成。

package main

import (
"fmt"
"time"
)
func worker(id int, ch chan bool) {
fmt.Printf("Worker %d 正在工作\n", id)
time.Sleep(time.Second * time.Duration(id))
ch <- true
}
func main() {
ch1 := make(chan bool)
ch2 := make(chan bool)
go worker(1, ch1)
go worker(2, ch2)
select {
case <-ch1:
fmt.Println("Worker 1 完成")
case <-ch2:
fmt.Println("Worker 2 完成")
}
fmt.Println("工作完成")
}

在這個範例中,我們啟動了兩個Goroutines(worker(1, ch1) 和 worker(2, ch2)),它們各自執行一些工作並將布林值寫入Channel。接著,我們使用 select 來等待哪個Channel中的值會先到達,即哪個Goroutine先完成工作。

select的特性, 只會選擇其中一個 case,如果兩個Goroutines都完成,只會選擇一個來處理,而不會等待所有Goroutines完成。這也使得 select 非常適合用來需要競爭的情況,可以根據需要增加更多的 case 來等待多個Goroutines。

time.Sleep

time.Sleep也是一種簡單的等待方式,但它只是預測多久完成,而且會阻塞整個執行緒,不推。

package main

import (
"fmt"
"time"
)
func worker(id int) {
fmt.Printf("Worker %d 正在工作\n", id)
time.Sleep(time.Second * time.Duration(id))
fmt.Printf("Worker %d 完成\n", id)
}
func main() {
go worker(1)
go worker(2)
// 等待一段時間以確保工作完成
time.Sleep(time.Second * 3)
fmt.Println("所有工作完成")
}

Goroutine 等待方式的比較

不同的等待方式有各自的使用場景和優缺點。sync.WaitGroup是最常用的方式,因為它簡單且高效。Channel等待方式在需要更多控制時很有用,而select則適用於等待多個Goroutine中的某一個。請根據您的需求選擇最適合的方式。

當你在Go語言中使用Goroutines並需要等待它們完成時,有幾種不同的等待方式可供選擇。以下是這些等待方式的比較:

  1. sync.WaitGroup

優點

  • 簡單易用
  • 效率高:等待期間不會浪費CPU資源。
  • 可用於任意數量的Goroutines。

限制

  • 需要額外的記憶體:每個等待的Goroutine都需要一個WaitGroup。
  • 只能等待固定數量的Goroutines。

2. Channel(Channel)等待

優點

  • 可以靈活控制等待:你可以在Goroutine完成時向Channel發送信號。
  • 可以用於不定數量的Goroutines。
  • 可以用於任何需要自定義等待邏輯的情況。

限制

  • 需要額外的程式碼來處理Channel。
  • 可能需要額外的同步以確保所有Channel操作正確。

3. select 多Channel選擇

優點

  • 可以等待多個Goroutines中的任何一個完成。
  • 適用於需要競爭的情況。

限制

  • 複雜性:當你有多個Channel時,可能需要更多的程式碼和邏輯。
  • 只能等待一個完成,無法等待多個Goroutines全部完成。

4. time.Sleep

優點

  • 簡單明瞭,容易理解。

限制

  • 不是一種有效的等待方式,因為會阻塞整個函數,浪費CPU資源。
  • 無法確保Goroutines何時完成。
  • 不推薦在正式環境中使用。

這樣看下來,最常見且推薦的方式就是使用sync.WaitGroup,因為簡單而且高效。但根據不同情況,你可能需要使用其他方式。使用Channel等待和select適用於需要更多控制和競爭的情況,而time.Sleep只應該在測試或學習目的中使用。選擇適合你需求的等待方式,才能確保Goroutines的並發能夠在你的應用中正確運作。

小結

前兩篇介紹了Goroutine、Channel,最後就來介紹一下如何確保你的工作有完成,紙條有成功送到對方手上。了解如何等待它們完成是開發高效併發應用的重要一環。介紹了多種等待Goroutine的方式,每種方式都有他的優勢和限制。可以根據需求,選擇適合的方式來讓你程式碼能夠充分利用Goroutine的優點。

📚Reference

avatar-img
17會員
83內容數
golang
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
Alan的開發者天地 的其他內容
👨‍💻簡介 昨天講到Goroutine有稍微簡單介紹Channel,Channel是Go語言中極為重要的併發通訊機制,它就像是不同goroutines之間的話筒,允許它們安全地傳遞資料和信息。這個強大的工具使得Go語言在處理併發任務時非常優雅和高效。
👨‍💻簡介 在日常生活中,如果能同時做很多事情,效率肯定大大提升,那麼在Go語言中,該如何做到呢,答案就是今天的主角Goroutine了,在Go語言中,讓併發變得簡單的強大工具,今天就是來給他一個快速介紹。
👨‍💻簡介 當我們在宣告變數時,電腦會為該變數在記憶體中分配一個位置,然後將這個變數值儲存在這個位置上,需要讀取或修改這個變數值時,電腦是透過記憶體位置來存取這個值。 今天來簡單介紹一下go的Pointer,他的特性以及常見用法。
👨‍💻簡介 在軟體開發中,錯誤無所不在。無論是網路請求失敗、檔案不存在,還是數學計算錯誤,處理錯誤是任何開發者的日常工作,系統的穩定度基本取決於對於錯誤處理是否全面,好的錯誤處理也可以產生適當的錯誤訊息,讓 Debug 更加容易。
👨‍💻簡介 當我們在寫程式時,有時候會需要在程式結束時關閉某些資源,而defer這個關鍵字,可以讓你輕鬆的實現,下面來簡單介紹一下defer以及常用的範例。,它為程式設計師提供了一種簡單而強大的工具,用於管理資源和確保程式的正確執行。
👨‍💻簡介 在Go中,假如我要判斷一個資料類型是甚麼,該怎麼做呢? Golang有一個功能叫做Type Assertions(類型斷言),它的作用就是能夠在運行時檢查我的資料類型,讓我在傳遞類型時能確保資料類型是正確的。
👨‍💻簡介 昨天講到Goroutine有稍微簡單介紹Channel,Channel是Go語言中極為重要的併發通訊機制,它就像是不同goroutines之間的話筒,允許它們安全地傳遞資料和信息。這個強大的工具使得Go語言在處理併發任務時非常優雅和高效。
👨‍💻簡介 在日常生活中,如果能同時做很多事情,效率肯定大大提升,那麼在Go語言中,該如何做到呢,答案就是今天的主角Goroutine了,在Go語言中,讓併發變得簡單的強大工具,今天就是來給他一個快速介紹。
👨‍💻簡介 當我們在宣告變數時,電腦會為該變數在記憶體中分配一個位置,然後將這個變數值儲存在這個位置上,需要讀取或修改這個變數值時,電腦是透過記憶體位置來存取這個值。 今天來簡單介紹一下go的Pointer,他的特性以及常見用法。
👨‍💻簡介 在軟體開發中,錯誤無所不在。無論是網路請求失敗、檔案不存在,還是數學計算錯誤,處理錯誤是任何開發者的日常工作,系統的穩定度基本取決於對於錯誤處理是否全面,好的錯誤處理也可以產生適當的錯誤訊息,讓 Debug 更加容易。
👨‍💻簡介 當我們在寫程式時,有時候會需要在程式結束時關閉某些資源,而defer這個關鍵字,可以讓你輕鬆的實現,下面來簡單介紹一下defer以及常用的範例。,它為程式設計師提供了一種簡單而強大的工具,用於管理資源和確保程式的正確執行。
👨‍💻簡介 在Go中,假如我要判斷一個資料類型是甚麼,該怎麼做呢? Golang有一個功能叫做Type Assertions(類型斷言),它的作用就是能夠在運行時檢查我的資料類型,讓我在傳遞類型時能確保資料類型是正確的。
你可能也想看
Google News 追蹤
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
下載 go get github.com/gomodule/redigo 連接 package main import ( "fmt" "github.com/gomodule/redigo/redis" ) func main() { c, err := redis.Dial(
Thumbnail
👨‍💻簡介 今天來介紹一個自己開發後端蠻常用的一個 package,promptui,拿來做menu真的很方便,promptui有兩個主要的輸入模式: Prompt:跳出單行使用者輸入。 Select:提供一個選項列表供使用者選擇。
Thumbnail
👨‍💻簡介 在 Go 語言中,reflect package是用來檢查和操作變數的type、value和struct。常見用法有檢察 type、調用方法,以及修改變數的value。今天簡單介紹 reflect package的主要功能、使用方法和常見用法。
Thumbnail
👨‍💻簡介 要處理日期和時間,就必須知道這個Package -> time,Go提供了內建的timePackage。 今天主要介紹time的功能,包括時間操作、格式化等等,以及常見用法。
Thumbnail
在進入主題之前建議先行閱讀「【程式語言 - Go】來認識Google開發的程式語言…」,初步認識一下Go語言是什麼? 容不容易學習? 才能夠更快的體會此篇章的目的。 當我們在進行軟體開發時,常常會需要有背後的資料庫系統來儲存我們的資料,而資料庫系統也會隨著時代的演進,進行大幅度的更新,那在這樣
Thumbnail
Golang(Go)是由Google開發的一種開源的、靜態型別的編程語言,目的在提供一個簡單、高效、安全以及易於擴展的程式語言,特別適用於並發和平行處理,Go語言設計上著重於開發者的生產力,並提供了現代化的語法和豐富的標準庫。 強調簡單易讀,讓我們回歸原始以最簡易的方式來撰寫程式,併發的特性讓日趨
Thumbnail
前言介紹 在 golang 1.16之後官方提供的工具包裡面有個 `embed` 可以使用,這使得把檔案嵌入 golang 的二進制編譯更為容易,以至於方便我們部署一些並非 .go 的副檔名檔案。 這裡要介紹的是如何把 vue 作為前端,編譯至我們的 golang 專案內,起一個網頁服務。 在開始
Thumbnail
由於Go語言本身沒有提供Enum的功能, 故我們可以使用package及type的技巧來達到類似的功能,假設今天要定義季節的enum型別, 包含了「春、夏、秋、冬」四種值的時候,可以怎麼做呢? 首先我們可以用package來框住season的範圍: 然而在season.go可以定義一個字串的類型 最
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
下載 go get github.com/gomodule/redigo 連接 package main import ( "fmt" "github.com/gomodule/redigo/redis" ) func main() { c, err := redis.Dial(
Thumbnail
👨‍💻簡介 今天來介紹一個自己開發後端蠻常用的一個 package,promptui,拿來做menu真的很方便,promptui有兩個主要的輸入模式: Prompt:跳出單行使用者輸入。 Select:提供一個選項列表供使用者選擇。
Thumbnail
👨‍💻簡介 在 Go 語言中,reflect package是用來檢查和操作變數的type、value和struct。常見用法有檢察 type、調用方法,以及修改變數的value。今天簡單介紹 reflect package的主要功能、使用方法和常見用法。
Thumbnail
👨‍💻簡介 要處理日期和時間,就必須知道這個Package -> time,Go提供了內建的timePackage。 今天主要介紹time的功能,包括時間操作、格式化等等,以及常見用法。
Thumbnail
在進入主題之前建議先行閱讀「【程式語言 - Go】來認識Google開發的程式語言…」,初步認識一下Go語言是什麼? 容不容易學習? 才能夠更快的體會此篇章的目的。 當我們在進行軟體開發時,常常會需要有背後的資料庫系統來儲存我們的資料,而資料庫系統也會隨著時代的演進,進行大幅度的更新,那在這樣
Thumbnail
Golang(Go)是由Google開發的一種開源的、靜態型別的編程語言,目的在提供一個簡單、高效、安全以及易於擴展的程式語言,特別適用於並發和平行處理,Go語言設計上著重於開發者的生產力,並提供了現代化的語法和豐富的標準庫。 強調簡單易讀,讓我們回歸原始以最簡易的方式來撰寫程式,併發的特性讓日趨
Thumbnail
前言介紹 在 golang 1.16之後官方提供的工具包裡面有個 `embed` 可以使用,這使得把檔案嵌入 golang 的二進制編譯更為容易,以至於方便我們部署一些並非 .go 的副檔名檔案。 這裡要介紹的是如何把 vue 作為前端,編譯至我們的 golang 專案內,起一個網頁服務。 在開始
Thumbnail
由於Go語言本身沒有提供Enum的功能, 故我們可以使用package及type的技巧來達到類似的功能,假設今天要定義季節的enum型別, 包含了「春、夏、秋、冬」四種值的時候,可以怎麼做呢? 首先我們可以用package來框住season的範圍: 然而在season.go可以定義一個字串的類型 最