如何在并发操作中关闭通道?

How to close the channel in a concurrent operation?

我写了一个关于并发和通道的go代码⤵️

package main

import (
    "fmt"
    "net/http"
)

var links = []string{
    "https://mcevik.com",
    "https://whosebug.com",
    "https://www.linkedin.com",
    "https://github.com",
    "https://medium.com",
    "https://kaggle.com",
}

func getLink(link string, ch chan string) {
    if res, err := http.Get(link); err != nil {
        ch <- err.Error()
    } else {
        ch <- fmt.Sprintf("[%d] - %s", res.StatusCode, link)
    }
}

func main() {
    ch := make(chan string, len(links))

    for _, link := range links {
        go getLink(link, ch)
    }

    for msg := range ch {
        fmt.Println(msg)
    }
}

https://play.golang.org/p/Uz_k8KI6bKt

输出就是这样⤵️


在输出中我们看到程序没有终止。程序没有终止的原因是通道没有关闭,无法退出循环

如何关闭频道并修复代码?

如果你恰好启动N(即len(links))个Go例程,所有这些例程都必然会发回一条消息,那么最简单的事情就是在关闭通道之前从通道中读取恰好N条消息。

不要range越过频道;当您不知道会收到多少项目并且想在频道关闭之前阅读时,这是最有用的。而是循环给定的次数:

// main:

for _ = range links {
    fmt.Println(<-ch)
}

close(ch)

使用 WaitGroup 来观察写入完成。

    ch := make(chan string, len(links))
    var wg sync.WaitGroup
    for _, link := range links {
        wg.Add(1)
        go func(){
            getLink(link, ch)
            wg.Done()
        }()
    }

使用另一个例程来侦听该事件并关闭通道。

    go func(){
        wg.Wait()
        close(ch)
    }()
    for msg := range ch {
        fmt.Println(msg)
    }

通过将 WaitGroup 添加到 getLink 方法对其进行重构,

func getLink(link string, wg *sync.WaitGroup, ch chan string)

并且频道在 wg.Wait() 通话后关闭。

go func() {
    wg.Wait()
    close(ch)
}()

结果,最终版本的代码是这样的⤵️

package main

import (
    "fmt"
    "net/http"
    "sync"
)

var links = []string{
    "https://mcevik.com",
    "https://whosebug.com",
    "https://www.linkedin.com",
    "https://github.com",
    "https://medium.com",
    "https://kaggle.com",
}

func getLink(link string, wg *sync.WaitGroup, ch chan string) {
    defer wg.Done()

    if res, err := http.Get(link); err != nil {
        ch <- err.Error()
    } else {
        ch <- fmt.Sprintf("[%d] - %s", res.StatusCode, link)
    }
}

func main() {
    wg := sync.WaitGroup{}
    ch := make(chan string, len(links))

    for _, link := range links {
        wg.Add(1)
        go getLink(link, &wg, ch)
    }

    go func() {
        wg.Wait()
        close(ch)
    }()

    for msg := range ch {
        fmt.Println(msg)
    }
}

https://play.golang.org/p/741F8eHrhFP