控制并发有三种种经典的方式,一种是通过channel通知实现并发控制 一种是WaitGroup,另外一种就是Context。

1. 使用最基本通过channel通知实现并发控制

无缓冲通道

无缓冲的通道指的是通道的大小为0,也就是说,这种类型的通道在接收前没有能力保存任何值,它要求发送 goroutine 和接收 goroutine 同时准备好,才可以完成发送和接收操作。

从上面无缓冲的通道定义来看,发送 goroutine 和接收 gouroutine 必须是同步的,同时准备后,如果没有同时准备好的话,先执行的操作就会阻塞等待,直到另一个相对应的操作准备好为止。这种无缓冲的通道我们也称之为同步通道。

正式通过无缓冲通道来实现多 goroutine 并发控制

func main() {
    ch := make(chan struct{})
    go func() {
        fmt.Println("do something..")
        time.Sleep(time.Second * 1)
        ch <- struct{}{}
    }()

<-ch

fmt.Println("I am finished")
}

当主 goroutine 运行到 <-ch 接受 channel 的值的时候,如果该  channel 中没有数据,就会一直阻塞等待,直到有值。 这样就可以简单实现并发控制

2. 通过sync包中的WaitGroup实现并发控制

在 sync 包中,提供了 WaitGroup ,它会等待它收集的所有 goroutine 任务全部完成。在WaitGroup里主要有三个方法

在主 goroutine 中 Add(delta int) 索要等待goroutine 的数量。在每一个 goroutine 完成后 Done() 表示这一个goroutine 已经完成,当所有的 goroutine 都完成后,在主 goroutine 中 WaitGroup 返回返回。

func main(){
    var wg sync.WaitGroup
    var urls = []string{
        "http://www.golang.org/",
        "http://www.google.com/",
        "http://www.somestupidname.com/",
    }
    for _, url := range urls {
        wg.Add(1)
        go func(url string) {
            defer wg.Done()
            http.Get(url)
        }(url)
    }
    wg.Wait()
}

但是在Golang官网中,有这么一句话

翻译够来过来就是,在 WaitGroup 第一次使用后,不能被拷贝,因为会出现一下问题

func main() {
    wg := sync.WaitGroup{}
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(wg sync.WaitGroup, i int) {
            log.Printf("i:%d", i)
            wg.Done()
        }(wg, i)
    }
    wg.Wait()
    log.Println("exit")
}

运行结果如下

2009/11/1