前言

上一篇文章简单介绍了一下 golang实现协程的方法:

上次提到过一个问题,由于创建一个新的 goroutine需要时间,所以导致出现了一个问题就是,go出来的函数还没有运行,而 main函数却已经运行完了,主函数结束,子函数也就结束了,所以子函数没有时间运行,上一篇文章使用的休眠来让 go出来的子函数有足够的时间运行完成,但是我们在实际的开发中,并不能够知道子函数会用多少时间,我们该留下多少时间让子函数运行,所以很明显,休眠是不正确

解决办法

这里要用到一个库:syncsync包提供了一个类型:WaitGroup
实现也很简单:

var 名称 sync.WaitGroup

这里WaitGroup提供了三个方法:

  • Add:当初始化完成时,会有一个计数器,初始为0,调用Add方法后,计数器+1
  • Done:调用此方法后,计数器-1
  • Wait:等待阻塞当前进程,等待计数器为0时,继续执行

所以通过这三个方法,我们可以大致清楚是怎么控制协程的

  1. 主函数创建 WaitGroup实列
  2. 每创建一个子函数,调用Add方法将计数器+1,并将WaitGroup传入子函数,注意此处,以引用的方法传入,避免发生副本拷贝而造成死锁
  3. 在子函数功能实现完成后,调用Done方法,将计数器-1,说明一个协程执行完毕,可以继续
  4. 在主函数中调用Wait方法,表示要等待计数器归零才能继续执行

看代码:

package main

import (
    "fmt"
    "sync"
)

func gohandle(name string, wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 0; i < 5; i++ {
        fmt.Printf("In goroutine %s\n", name)
    }
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 2; i++ {
        wg.Add(1)
        str := fmt.Sprintf("协程%d号", i+1)
        go gohandle(str, &wg)
    }
    wg.Wait()
}

这样我们就不用再用休眠来是完成了

协程数量

前面说了,怎么优雅的等待协程执行完成,那么更近一步,控制协程的数量这时候就要用到信道+WaitGroup来实现
代码:

package main

import (
    "fmt"
    "strconv"
    "sync"
    "time"
)


func handle(data string, ch chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Println(data)
    time.Sleep(time.Second)
    <-ch
}

func main() {
    ch := make(chan int, 3)
    var wg sync.WaitGroup
    datas := [10]string{}
    for i := 0; i < 10; i++ {
        datas[i] = strconv.Itoa(i)
    }
    for i := 0; i < len(datas); i++ {
        ch <- 1
        wg.Add(1)
        go handle(datas[i], ch, &wg)
    }

    wg.Wait()
}

这里使用有缓冲区信道来控制协程的数量,由于信道的数量呗固定了,所以必须等待信道中有空位时,下一个协程才能继续进行

最后修改:2020 年 07 月 30 日
如果觉得我的文章对你有用,请随意赞赏