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 协程的数量

欢送点赞,关注,珍藏

敌人们,你的反对和激励,是我保持分享,提高质量的能源

好了,本次就到这里

技术是凋谢的,咱们的心态,更应是凋谢的。拥抱变动,背阴而生,致力向前行。

我是阿兵云原生,欢送点赞关注珍藏,下次见~