Go 語言通知協程退出 -取消- 的幾種方式
在 Go 語言中,控制 goroutine 的退出或取消很重要,這能使資源得到合理利用,避免潛在的內存泄露。
如下是一些在 Go 中通知協程退出的常見方式:
-
使用通道(
Channel):通過發送特定的信號或關閉通道來通知協程退出。這是最簡單直接的方法。 -
使用
context包:context包提供了一種更標準化的方式來傳遞取消信號、超時、截止時間等控制信息。 -
** 使用
sync.WaitGroup**:雖然WaitGroup本身不用於發送取消信號,但它可以用來等待一組協程完成,通常與其他方法(如通道)結合使用來控制協程的退出。
1. 使用通道
package main
import (
"fmt"
"time"
)
func worker(exitChan chan bool) {
for {
select {
case <-exitChan:
fmt.Println("Worker exiting.")
return
default:
fmt.Println("Working...")
time.Sleep(1 * time.Second)
}
}
}
func main() {
exitChan := make(chan bool)
go worker(exitChan)
time.Sleep(3 * time.Second) // 模擬做了一些工作
exitChan <- true // 通知協程退出
time.Sleep(1 * time.Second) // 給協程時間退出
}
輸出:
Working...
Working...
Working...
Working...
Worker exiting.
在線代碼 [1]
2. 使用 context 包
package main
import (
"context"
"fmt"
"time"
)
func worker(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Worker exiting.")
return
default:
fmt.Println("Working...")
time.Sleep(1 * time.Second)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go worker(ctx)
time.Sleep(3 * time.Second) // 模擬做了一些工作
cancel() // 通知協程退出
time.Sleep(1 * time.Second) // 給協程時間退出
}
輸出:
Working...
Working...
Working...
Working...
Worker exiting.
在線代碼 [2]
在上面這兩個示例中,當主函數完成其工作後,通過通道發送信號或調用 cancel 函數來通知協程退出。使用 context 包是更推薦的做法,因爲其提供了一種更標準化和靈活的方式來管理協程的生命週期。
3. 使用 sync.WaitGroup 控制協程退出
sync.WaitGroup 主要用於等待一組協程的完成。其不直接提供通知協程退出的機制,但可以與其他方法(如通道)結合使用來控制協程的退出。
示例:
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup, stopCh chan struct{}) {
defer wg.Done()
for {
select {
case <-stopCh: // 接收退出信號
fmt.Printf("Worker %d stopping\n", id)
return
default:
fmt.Printf("Worker %d working\n", id)
time.Sleep(time.Second)
}
}
}
func main() {
var wg sync.WaitGroup
stopCh := make(chan struct{})
workerCount := 3
wg.Add(workerCount)
for i := 0; i < workerCount; i++ {
go worker(i, &wg, stopCh)
}
time.Sleep(3 * time.Second) // 模擬工作
close(stopCh) // 發送退出信號給所有協程
wg.Wait() // 等待所有協程退出
fmt.Println("All workers stopped")
}
輸出:
Worker 0 working
Worker 2 working
Worker 1 working
Worker 2 working
Worker 0 working
Worker 1 working
Worker 0 working
Worker 2 working
Worker 1 working
Worker 2 stopping
Worker 0 stopping
Worker 1 stopping
All workers stopped
在線代碼 [3]
在上例中,stopCh 通道用於通知協程退出。當關閉 stopCh 時,所有監聽這個通道的協程都會接收到信號,並優雅地停止執行。
但我覺得這個例子並不好, 本質上成了和 **1. 使用通道(Channel)**一樣了..
sync.WaitGroup的真正作用是卡在wg.Wait()處,直到wg.Done()被執行 (wg.Add()增加的值被減爲 0), 纔會繼續往下執行. 比如往往用於防止 goroutine 還沒執行完, 主協程就退出了
另外, 如果是性能敏感場景, 往往使用原子操作(Atomic)在多個協程之間安全地共享狀態 (原子操作用於安全地讀寫共享狀態,可以用來設置一個標誌,協程可以定期檢查這個標誌來決定是否退出), 而不使用通道來做協程間的通信
更多參考:
golang context 父子任務同步取消信號 [4]
Go 程序員面試筆試寶典 - context 如何被取消 [5]
如何退出協程 goroutine (其他場景)[6]
go 協程取消 [7]
參考資料
[1]
在線代碼: https://go.dev/play/p/HrZbNO-jyKf
[2]
在線代碼: https://go.dev/play/p/hg_w1bxcmyg
[3]
在線代碼: https://go.dev/play/p/9AwV2v9iqdu
[4]
golang context 父子任務同步取消信號: https://blog.csdn.net/whatday/article/details/113771225
[5]
Go 程序員面試筆試寶典 - context 如何被取消: https://golang.design/go-questions/stdlib/context/cancel/
[6]
如何退出協程 goroutine (其他場景): https://geektutu.com/post/hpg-exit-goroutine.html
[7]
go 協程取消: https://www.google.com/search?q=go%E5%8D%8F%E7%A8%8B%E5%8F%96%E6%B6%88
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/CNTlXMWSmQ4dNbbCQpbjYg