Goroutine并发控制
创建协程
jobCount := 10
// sync.WaitGroup 监控所有协程的状态,从而保证主协程结束时所有的子协程已经退出
group := sync.WaitGroup{}
for i:=0;i < jobCount;i++ {
group.Add(1)
go func(i int) {
fmt.Println("task ",i)
time.Sleep(time.Second) // 刻意睡 1 秒钟,模拟耗时
group.Done()
}(i)
fmt.Printf("index: %d,goroutine Num: %d \n", i, runtime.NumGoroutine())
}
group.Wait()
运行结果:
index: 0,goroutine Num: 2
index: 1,goroutine Num: 3
task 0
index: 2,goroutine Num: 4
index: 3,goroutine Num: 5
task 3
task 2
index: 4,goroutine Num: 6
index: 5,goroutine Num: 7
index: 6,goroutine Num: 8
task 4
index: 7,goroutine Num: 9
task 6
index: 8,goroutine Num: 10
index: 9,goroutine Num: 11
task 7
task 8
task 1
task 9
task 5
可以看到总共有11个协程,其中一个是主协程,其他十个是子协程,为了让主协程等待所有的子协程执行完毕后再退出,使用 sync.WaitGroup 监控所有协程的状态,从而保证主协程结束时所有的子协程已经退出。
实际生产中,产生的goroutine的数量,是巨大,但是这种简单直接的方式就显得不那么高效了。CPU同一时间只能处理一个Goroutine,大量job的情况将会出现大量的Goroutine等待,以致于造成资源的浪费。
控制Goroutine数量
令牌桶
chan+goroutine+sync.WaitGroup方式
jobCount := 10
// sync.WaitGroup 监控所有协程的状态,从而保证主协程结束时所有的子协程已经退出
group := sync.WaitGroup{}
// 定一个桶 桶的容量为2,桶满了阻塞,类似令牌桶。保证每次同时运行的gorutine数量不会超过桶容量,达到每次并发控制gorutine数量
buckets := make(chan bool,2)
for i:=0;i < jobCount;i++ {
buckets <- true
group.Add(1)
go func(i int) {
fmt.Println("task ",i)
time.Sleep(time.Second) // 刻意睡 1 秒钟,模拟耗时
//fmt.Printf("index: %d,goroutine Num: %d \n", i, runtime.NumGoroutine())
<- buckets
group.Done()
}(i)
fmt.Printf("index: %d,goroutine Num: %d \n", i, runtime.NumGoroutine())
}
group.Wait()
index: 0,goroutine Num: 2
index: 1,goroutine Num: 3
task 1
task 0
task 2
index: 2,goroutine Num: 2
index: 3,goroutine Num: 3
task 3
index: 4,goroutine Num: 3
index: 5,goroutine Num: 3
task 5
task 4
index: 6,goroutine Num: 2
index: 7,goroutine Num: 3
task 6
task 7
index: 8,goroutine Num: 3
task 8
index: 9,goroutine Num: 3
task 9
可以看到做到了控制2个2个执行的效果。
多worker消费
起多个worker等待chan;消息发布到chan,根据chan容量阻塞;worker开始消费chan;group.Wait()等待goroutine结束。
// 控制 Goroutine 数量
jobCount := 10
group := sync.WaitGroup{}
// chan容量为3
jobsChan := make(chan int,5)
// 起3个worker
workerCount := 3
for w:=0; w <= workerCount; w++ {
go func(w int){
for j := range jobsChan {
fmt.Printf("worker %d get chan msg %d \n",w,j)
time.Sleep(time.Second)
group.Done()
}
}(w)
}
// 发布消息到chan
for j :=0; j < jobCount; j++ {
jobsChan <- j
group.Add(1)
fmt.Printf("index: %d,goroutine Num: %d\n", j, runtime.NumGoroutine())
}
group.Wait()
worker 1 get chan msg 0
index: 0,goroutine Num: 5
index: 1,goroutine Num: 6
worker 0 get chan msg 1
worker 2 get chan msg 2
index: 2,goroutine Num: 5
index: 3,goroutine Num: 5
index: 4,goroutine Num: 5
index: 5,goroutine Num: 5
index: 6,goroutine Num: 5
worker 3 get chan msg 3
worker 0 get chan msg 4
worker 1 get chan msg 7
index: 7,goroutine Num: 5
worker 2 get chan msg 5
index: 8,goroutine Num: 5
index: 9,goroutine Num: 5
worker 3 get chan msg 6
worker 0 get chan msg 8
worker 3 get chan msg 9
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程