乐趣区

关于go:你知道-GO-中的-协程可以无止境的开吗

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

欢送点赞,关注,珍藏

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

好了,本次就到这里

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

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

退出移动版