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 协程的数量
欢送点赞,关注,珍藏
敌人们,你的反对和激励,是我保持分享,提高质量的能源
好了,本次就到这里
技术是凋谢的,咱们的心态,更应是凋谢的。拥抱变动,背阴而生,致力向前行。
我是阿兵云原生,欢送点赞关注珍藏,下次见~