Go 語言中控制協程數量的常用方法小結
在 Go 語言中,協程(goroutine)是輕量級的執(zhí)行單元,雖然開銷小,但無限制地創(chuàng)建協程仍然會消耗大量系統(tǒng)資源,甚至導致程序崩潰。因此,合理控制協程數量是編寫高效 Go 程序的關鍵。本文將介紹幾種常用的協程數量控制方法,并結合具體案例說明其用法。
一、使用帶緩沖的通道控制
帶緩沖的通道可以作為一個簡易的信號量(Semaphore),通過控制通道的容量來限制同時運行的協程數量。
基本原理:
- 創(chuàng)建一個指定容量的通道
- 啟動協程前先向通道發(fā)送信號(獲取令牌)
- 協程結束后從通道接收信號(釋放令牌)
- 當通道已滿時,新的協程需要等待直到有令牌釋放
案例代碼:
package main
import (
"fmt"
"time"
)
func worker(id int, sem chan struct{}) {
defer func() { <-sem }() // 釋放令牌
fmt.Printf("Worker %d 開始工作\n", id)
time.Sleep(time.Second) // 模擬工作
fmt.Printf("Worker %d 完成工作\n", id)
}
func main() {
const maxGoroutines = 3 // 最大協程數量
sem := make(chan struct{}, maxGoroutines)
totalTasks := 10 // 總任務數
for i := 0; i < totalTasks; i++ {
sem <- struct{}{} // 獲取令牌,若滿則等待
go worker(i, sem)
}
// 等待所有令牌被釋放(所有協程完成)
for i := 0; i < cap(sem); i++ {
sem <- struct{}{}
}
fmt.Println("所有任務完成")
}
二、使用 sync.WaitGroup 配合通道
sync.WaitGroup 用于等待一組協程完成,結合通道可以更靈活地控制協程數量。
案例代碼:
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d 開始工作\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d 完成工作\n", id)
}
func main() {
const maxGoroutines = 3
sem := make(chan struct{}, maxGoroutines)
var wg sync.WaitGroup
totalTasks := 10
for i := 0; i < totalTasks; i++ {
sem <- struct{}{}
wg.Add(1)
go func(id int) {
defer func() { <-sem }()
worker(id, &wg)
}(i)
}
wg.Wait() // 等待所有任務完成
fmt.Println("所有任務完成")
}
三、使用工作池(Worker Pool)模式
工作池模式創(chuàng)建固定數量的工作協程,從任務隊列中獲取任務執(zhí)行,適用于任務數量多且可批量處理的場景。
案例代碼:
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
fmt.Printf("Worker %d 處理任務 %d\n", id, job)
time.Sleep(time.Second) // 模擬處理時間
results <- job * 2 // 模擬處理結果
}
}
func main() {
const (
numWorkers = 3 // 工作協程數量
numJobs = 10 // 任務數量
)
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
// 啟動工作協程
wg.Add(numWorkers)
for w := 1; w <= numWorkers; w++ {
go worker(w, jobs, results, &wg)
}
// 發(fā)送任務
go func() {
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs) // 所有任務發(fā)送完畢,關閉通道
}()
// 等待所有工作協程完成
go func() {
wg.Wait()
close(results) // 所有結果處理完畢,關閉通道
}()
// 收集結果
for result := range results {
fmt.Printf("收到結果: %d\n", result)
}
fmt.Println("所有任務完成")
}
四、使用第三方庫
對于復雜場景,可以使用成熟的第三方庫,如 golang.org/x/sync/errgroup 或 github.com/panjf2000/ants(高性能協程池)。
使用 errgroup 的案例:
package main
import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
"time"
)
func worker(id int) error {
fmt.Printf("Worker %d 開始工作\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d 完成工作\n", id)
return nil
}
func main() {
const maxGoroutines = 3
g, ctx := errgroup.WithContext(context.Background())
g.SetLimit(maxGoroutines) // 設置最大并發(fā)數
totalTasks := 10
for i := 0; i < totalTasks; i++ {
id := i
g.Go(func() error {
select {
case <-ctx.Done():
return ctx.Err()
default:
return worker(id)
}
})
}
if err := g.Wait(); err != nil {
fmt.Printf("發(fā)生錯誤: %v\n", err)
} else {
fmt.Println("所有任務完成")
}
}
到此這篇關于Go 語言中控制協程數量的常用方法小結的文章就介紹到這了,更多相關Go 控制協程數量內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Qt6.5 grpc組件使用 + golang grpc server
這篇文章主要介紹了Qt6.5 grpc組件使用+golang grpc server示例,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-05-05
Go?tablewriter庫提升命令行輸出專業(yè)度實例詳解
命令行工具大家都用過,如果是運維人員可能會編寫命令行工具來完成各種任務,命令行輸出的美觀和易讀性往往容易被忽視,很爛的輸出會讓人感覺不專業(yè),本文將介紹Go語言中牛逼的實戰(zhàn)工具tablewriter庫,使你在命令行輸出中展現出專業(yè)的一面2023-11-11
Go結構體SliceHeader及StringHeader作用詳解
這篇文章主要為大家介紹了Go結構體SliceHeader及StringHeader作用的功能及面試官愛問的實際意義詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-07-07

