Go語言中Goroutine的等待方式
👨💻簡介
昨天講到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並需要等待它們完成時,有幾種不同的等待方式可供選擇。以下是這些等待方式的比較:
sync.WaitGroup:
優點:
- 簡單易用
- 效率高:等待期間不會浪費CPU資源。
- 可用於任意數量的Goroutines。
限制:
- 需要額外的記憶體:每個等待的Goroutine都需要一個WaitGroup。
- 只能等待固定數量的Goroutines。
Channel(Channel)等待:
優點:
- 可以靈活控制等待:你可以在Goroutine完成時向Channel發送信號。
- 可以用於不定數量的Goroutines。
- 可以用於任何需要自定義等待邏輯的情況。
限制:
- 需要額外的程式碼來處理Channel。
- 可能需要額外的同步以確保所有Channel操作正確。
select
多Channel選擇:優點:
- 可以等待多個Goroutines中的任何一個完成。
- 適用於需要競爭的情況。
限制:
- 複雜性:當你有多個Channel時,可能需要更多的程式碼和邏輯。
- 只能等待一個完成,無法等待多個Goroutines全部完成。
time.Sleep
:優點:
- 簡單明瞭,容易理解。
限制:
- 不是一種有效的等待方式,因為會阻塞整個函數,浪費CPU資源。
- 無法確保Goroutines何時完成。
- 不推薦在正式環境中使用。
這樣看下來,最常見且推薦的方式就是使用sync.WaitGroup
,因為簡單而且高效。但根據不同情況,你可能需要使用其他方式。使用Channel等待和select
適用於需要更多控制和競爭的情況,而time.Sleep
只應該在測試或學習目的中使用。選擇適合你需求的等待方式,才能確保Goroutines的並發能夠在你的應用中正確運作。
小結
前兩篇介紹了Goroutine、Channel,最後就來介紹一下如何確保你的工作有完成,紙條有成功送到對方手上。了解如何等待它們完成是開發高效併發應用的重要一環。介紹了多種等待Goroutine的方式,每種方式都有他的優勢和限制。可以根據需求,選擇適合的方式來讓你程式碼能夠充分利用Goroutine的優點。