Go語言中Goroutine的等待方式

閱讀時間約 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

17會員
83內容數
golang
留言0
查看全部
發表第一個留言支持創作者!
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
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
美國總統大選只剩下三天, 我們觀察一整週民調與金融市場的變化(包含賭局), 到本週五下午3:00前為止, 誰是美國總統幾乎大概可以猜到60-70%的機率, 本篇文章就是以大選結局為主軸來討論近期甚至到未來四年美股可能的改變
Thumbnail
Faker昨天真的太扯了,中國主播王多多點評的話更是精妙,分享給各位 王多多的點評 「Faker是我們的處境,他是LPL永遠繞不開的一個人和話題,所以我們特別渴望在決賽跟他相遇,去直面我們的處境。 我們曾經稱他為最高的山,最長的河,以為山海就是盡頭,可是Faker用他28歲的年齡...
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
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
美國總統大選只剩下三天, 我們觀察一整週民調與金融市場的變化(包含賭局), 到本週五下午3:00前為止, 誰是美國總統幾乎大概可以猜到60-70%的機率, 本篇文章就是以大選結局為主軸來討論近期甚至到未來四年美股可能的改變
Thumbnail
Faker昨天真的太扯了,中國主播王多多點評的話更是精妙,分享給各位 王多多的點評 「Faker是我們的處境,他是LPL永遠繞不開的一個人和話題,所以我們特別渴望在決賽跟他相遇,去直面我們的處境。 我們曾經稱他為最高的山,最長的河,以為山海就是盡頭,可是Faker用他28歲的年齡...
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可以定義一個字串的類型 最