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

参考:

posted on   进击的davis  阅读(101)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
历史上的今天:
2021-07-23 openssl生成自签名证书-流程

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示