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

huangapple go评论82阅读模式
英文:

How to close the channel in a concurrent operation?

问题

我写了一个关于并发和通道的 Go 代码,如下所示:

package main

import (
	"fmt"
	"net/http"
)

var links = []string{
	"https://mcevik.com",
	"https://stackoverflow.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)
	}
}

输出结果如下所示:

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


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

如何关闭通道并修复代码呢?

英文:

I wrote a go code like that about concurrency and channels ⤵️

package main

import (
	&quot;fmt&quot;
	&quot;net/http&quot;
)

var links = []string{
	&quot;https://mcevik.com&quot;,
	&quot;https://stackoverflow.com&quot;,
	&quot;https://www.linkedin.com&quot;,
	&quot;https://github.com&quot;,
	&quot;https://medium.com&quot;,
	&quot;https://kaggle.com&quot;,
}

func getLink(link string, ch chan string) {
	if res, err := http.Get(link); err != nil {
		ch &lt;- err.Error()
	} else {
		ch &lt;- fmt.Sprintf(&quot;[%d] - %s&quot;, 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

and the output is like that ⤵️

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


In the output we see that the program is not terminated. The reason for the program's not terminated is that the channel has not closed and therefore cannot exit the loop.

How can I close the channel and fix the code?

答案1

得分: 1

使用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)
    }
英文:

use a WaitGroup to watch for writes completion.

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

Use another routine to listen that event and close the channel.

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

答案2

得分: 0

如果你启动了确切数量为N(即len(links))的Go协程,而且所有协程都必须发送一条消息回来,那么最简单的方法是在关闭通道之前从通道中读取确切数量为N的消息。

不要使用range遍历通道;这在你不知道会接收到多少项并且想要一直读取直到通道关闭时最有用。相反,循环指定的次数:

// main:

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

close(ch)
英文:

If you start exactly N (ie len(links)) Go routines, all of which will necessarily send back a message, then the simplest thing is to read exactly N messages from the channel before closing it.

Don't range over the channel; that's most useful when you don't know how many items you'll receive and you want to read until the channel is closed. Instead loop a given number of times:

// main:

for _ = range links {
    fmt.Println(&lt;-ch)
}

close(ch)

答案3

得分: 0

通过在getLink方法中添加WaitGroup,对代码进行了重构,

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://stackoverflow.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

英文:

Refactored it by adding WaitGroup to getLink method,

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

and channel closed after wg.Wait() call.

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

As a result, the final version of the code looks like this ⤵️

package main

import (
	&quot;fmt&quot;
	&quot;net/http&quot;
	&quot;sync&quot;
)

var links = []string{
	&quot;https://mcevik.com&quot;,
	&quot;https://stackoverflow.com&quot;,
	&quot;https://www.linkedin.com&quot;,
	&quot;https://github.com&quot;,
	&quot;https://medium.com&quot;,
	&quot;https://kaggle.com&quot;,
}

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

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

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

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

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

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

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

huangapple
  • 本文由 发表于 2021年9月24日 23:12:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/69317367.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定