GO 语言天生高并发的语言,那么是不是应用 go 开拓协程越多越好的,那么在 go 外面,协程是不是能够开有限多个呢?
那么咱们就一起来看看尝试写写 demo 吧
尝试开拓尽可能多的 协程
写一个 demo,循环开 1 << 31 - 1
个协程看看会是什么成果
func main() {
goroutineNum := math.MaxInt32
for i := 0; i < goroutineNum; i++ {go func(i int) {fmt.Println("i ==", i, "func ==", runtime.NumGoroutine())
}(i)
}
}
执行后,我人都傻了,间接是没有输入,2 核 1 G 的服务器 间接卡死,感兴趣的 xdm 能够尝试一波
这里说一下,呈现上述景象的起因是:
咱们迅速的疯狂开拓协程,又不管制并发数量,那么在那段很短的工夫外面,go 程序会尽可能多的占用操作系统资源,直到被操作系统被动杀掉
一旦主协程被杀掉,那么其余的协程也全副 game over,因为他们占用的资源是用户态的共享资源,一个协程挂掉,是会影响到其余协程的
尝试管制协程数量
咱们实现的办法是,应用 channel,设置 channel 的缓冲个数来管制理论并发的协程个数,一起来看看是否有成果
func processGo(i int, ch chan struct{}) {fmt.Println("i ==", i, "func ==", runtime.NumGoroutine())
<-ch
}
func main() {
goroutineNum := math.MaxInt32
ch := make(chan struct{}, 5)
for i := 0; i < goroutineNum; i++ {ch <- struct{}{}
go processGo(i, ch)
}
}
成果见如下截图,因为数据打印太长,如下为局部数据
这里咱们能够看到,退出并发管制后,成果还是很显著的,至多我的服务器不会被卡死了
通过打印咱们能够看进去,总共 6 个协程,其中有 5 个是子协程,1 个是主协程
咱们这里,应用 channel 的形式来管制并发,go 协程的创立速度 依赖于 for 循环的速度,而 for 循环的速度是被 channel 管制住了,channel 的速度实际上又被理论解决事件的协程的处理速度管制着,因而,咱们能够保障在同一个工夫内,并发运行的协程总共是 6 个
然而这就够了么,nonono,咱们能够再来看一个例子
- 咱们设置在循环的个数为 10,比方才的值小了很多,代码逻辑保持一致
func main() {
goroutineNum := 10
ch := make(chan struct{}, 5)
for i := 0; i < goroutineNum; i++ {ch <- struct{}{}
go processGo(i, ch)
}
}
执行程序看成果
# go run main.go
i == 4 func == 6
i == 5 func == 6
i == 6 func == 6
i == 7 func == 6
i == 8 func == 6
咱们发现输入并不是咱们想要的,呈现这个的起因是主协程 循环 10 次结束之后,就会马上退出程序,进而子协程也随之退出,这个问题须要解决
尝试退出 sync 同步机制,让主协程等一下子协程
之前咱们有分享到 go 中的一个知识点,能够应用 sync 来一起管制同步,就是应用 sync.WaitGroup
,不晓得 xdm 是否还记得,不记得没关系,咱们明天再应用一遍,看看成果
- 退出 sync 机制,循环的时候,须要开拓协程时,则 sync.Add
- 协程完结的时候,sync.Done
- 主协程循环结束之后,期待子协程实现本人的事件,应用 sync.Wait
func processGo(i int, ch chan struct{}) {fmt.Println("i ==", i, "func ==", runtime.NumGoroutine())
<-ch
wg.Done()}
var wg = sync.WaitGroup{}
func main() {
goroutineNum := 10
ch := make(chan struct{}, 5)
for i := 0; i < goroutineNum; i++ {ch <- struct{}{}
wg.Add(1)
go processGo(i, ch)
}
wg.Wait()}
上述代码中,咱们能够简略了解 sync 的应用,sync.Add 就是增加须要期待多少个子协程完结,sync.Done 就是以后的子协程完结了,减去 1 个协程,sync.Wait 就是期待 子协程的个数最终变成 0,则认为子协程全副敞开
运行程序来查看成果
m# go run main.go
i == 4 func == 6
i == 5 func == 6
i == 6 func == 6
i == 7 func == 6
i == 8 func == 6
i == 9 func == 6
i == 0 func == 5
i == 1 func == 4
i == 2 func == 3
i == 3 func == 2
尝试做的更加可控一些更加优良一些
咱们能够思考一下,下面的逻辑是不停的有协程在创立,也不停的有协程在被销毁,这样还是很耗资源的,咱们是否能够固定设置具体的协程在做事件,并且将发送数据和解决数据进行一个拆散呢?
就相似于生产者和消费者一样
咱们来尝试写一个 demo
- 专门写一个函数用于散发工作
- 散发工作之前先开拓好对应的协程,期待工作进来
func processGo(i int, ch chan struct{}) {
for data := range ch {fmt.Println("i ==", data, "func ==", runtime.NumGoroutine())
wg.Done()}
}
func distributeTask(ch chan struct{}) {wg.Add(1)
ch <- struct{}{}
}
var wg = sync.WaitGroup{}
func main() {
goroutineNum := 2
taskNum := math.MaxInt32
ch := make(chan struct{})
// 先开拓好协程 期待解决数据
for i := 0; i < goroutineNum; i++ {go processGo(i, ch)
}
// 散发事项
for i := 0; i < taskNum; i++ {distributeTask(ch)
}
wg.Wait()}
此处应用 sync 管制的同步,能够说是 对应的是工作数量,主协程是期待所有散发的工作数都被实现了,主协程才关闭程序
执行程序查看成果
go run main.go
程序失常运行没有故障,这样做的话,咱们能够将散发工作和解决工作进行拆散,还大大减少了不必要的协程切换
对于如上案例做一个比喻
channel + sync 的案例 :
最下面的第一种案例,就是相当于动静雇佣 5 个工人,有工作的时候,工人就下来做,做完了本人下岗就得了,反正我这里只包容 5 个工人,且每个工人做完 1 个工作就得走
散发工作和解决数据的工作拆散案例:
最初的这个案例,就是固定的雇佣 2 个工人干活,项目经理就不停的扔工作进行来,这俩人就疯狂的干
xdm,go 外面不能滥用协程,须要管制好 go 协程的数量
欢送点赞,关注,珍藏
敌人们,你的反对和激励,是我保持分享,提高质量的能源
好了,本次就到这里
技术是凋谢的,咱们的心态,更应是凋谢的。拥抱变动,背阴而生,致力向前行。
我是 阿兵云原生,欢送点赞关注珍藏,下次见~