golang 中的并发控制与优雅结束
提到 golang 并发,就可以联想到下列关键词:
- goroutine
- channel
- sync.WaitGroup
- sync.Mutex
- tunny/ants协程池
- for-select
在一般的场景中,比如碰到这类场景,需要控制并发协程的数量,通常的话,我们可以通过 channel 或者 类似协程池功能,或者就是直接用现成的三方库-协程池,总之这些都是常用的一些方案,所以由此我们大概总结以下几种并发控制的方式。
并发控制
通过 channel 实现信号通知
先看代码:
package main import ( "fmt" "math/rand" "sync" "time" ) // sync.WaitGroup + channel func main() { // concurrency count var concurrency int = 3 // define sem sem := make(chan struct{}, concurrency) var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func(id int) { defer wg.Done() worker(id, sem) }(i) } wg.Wait() fmt.Println("Main goroutine end.") } func worker(id int, sem chan struct{}) { // 占用一个信号量 sem <- struct{}{} // 运行结束后,释放信号量 defer func() { <- sem }() // exec task rand.Seed(time.Now().UnixNano()) time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) fmt.Printf("Time: %v, Worker %d: working...\n", time.Now().Format(time.RFC3339Nano), id) }
通过定义一个给定缓冲的 channel 当做一个信号池,根据 channel 缓冲特性及阻塞特性,实现并发数量的限制或者控制。
自定义简单的协程池
先看代码:
package main import ( "fmt" "sync" ) func worker(id int, jobs <-chan int, results chan<- int) { //jobs等待主要协程往jobs放数据 for j := range jobs { fmt.Printf("协程池 %d: 协程池正在工作 %d\n", id, j) results <- j } } func main() { const numJobs = 5 //协程要做的工作数量 const numWorkers = 3 //协程池数量 jobs := make(chan int, numJobs) results := make(chan int, numJobs) var wg sync.WaitGroup // 启动协程池 for i := 1; i <= numWorkers; i++ { wg.Add(1) go func(id int) { defer wg.Done() worker(id, jobs, results) }(i) } // 提交任务 for j := 1; j <= numJobs; j++ { jobs <- j } close(jobs) // 等待所有工作完成 go func() { wg.Wait() close(results) }() // 处理结果 for result := range results { fmt.Println("Result:", result) } }
通过现有的 tunny/ants 协程池
package main import ( "log" "time" "github.com/Jeffail/tunny" ) func main() { pool := tunny.NewFunc(10, func(i interface{}) interface{} { log.Println(i) time.Sleep(time.Second) return nil }) defer pool.Close() for i := 0; i < 500; i++ { go pool.Process(i) } time.Sleep(time.Second * 4) }
并发协程的优雅结束
for-range
先看代码:
package main import ( "fmt" "time" ) // for-range channel func main() { jobs := make(chan int, 10) stop := make(chan struct{}) defer close(stop) // publish jobs go func() { for i := 0; i < 20; i++ { jobs <- i } close(jobs) }() // exec jobs go func(job <- chan int) { for num := range job { fmt.Printf("Time: %v, exec job: %d\n", time.Now().Format(time.RFC3339Nano), num) } stop <- struct{}{} }(jobs) // 等待发送信号结束 主goroutine <- stop fmt.Println("Main goroutine end.") }
主要利用 channel 关闭后,通过 for-range 操作自动退出特性实现子协程优雅退出。
for-select
参考:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2021-07-23 openssl生成自签名证书-流程