【发布时间】:2021-01-09 15:25:07
【问题描述】:
我不一定要尝试完成特定的事情,更多只是了解 goroutines、channels、waitgroups 和 select(on channels)如何一起发挥作用。我正在编写一个简单的程序,它遍历一段 URL,获取 URL,然后基本上就结束了。简单的想法是,我希望所有的提取都发生并返回,通过通道发送它们的数据,然后在所有提取发生后结束。我快到了,我知道我的选择中缺少一些将结束循环的东西,可以说“嘿,等待组现在是空的”,但我不确定如何最好地做到这一点。介意看一下并为我清理吗?现在一切都运行得很好,它只是不会终止,所以很明显我错过了一些东西和/或不理解其中一些组件应该如何协同工作。
package main
import (
"fmt"
"io/ioutil"
"net/http"
"sync"
)
var urls = []string{
"https://www.google.com1",
"https://www.gentoo.org",
}
var wg sync.WaitGroup
// simple struct to store fetching
type urlObject struct {
url string
success bool
body string
}
func getPage(url string, channelMain chan urlObject, channelError chan error) {
// increment waitgroup, defer decrementing
wg.Add(1)
defer wg.Done()
fmt.Println("fetching " + url)
// create a urlObject
uO := urlObject{
url: url,
success: false,
}
// get URL
response, getError := http.Get(url)
// close response later on
if response != nil {
defer response.Body.Close()
}
// send error over error channel if one occurs
if getError != nil {
channelError <- getError
return
}
// convert body to []byte
body, conversionError := ioutil.ReadAll(response.Body)
// convert []byte to string
bodyString := string(body)
// if a conversion error happens send it over the error channel
if conversionError != nil {
channelError <- conversionError
} else {
// if not send a urlObject over the main channel
uO.success = true
uO.body = bodyString
channelMain <- uO
}
}
func main() {
var channelMain = make(chan urlObject)
var channelError = make(chan error)
for _, v := range urls {
go getPage(v, channelMain, channelError)
}
// wait on goroutines to finish
wg.Wait()
for {
select {
case uO := <-channelMain:
fmt.Println("completed " + uO.url)
case err := <-channelError:
fmt.Println("error: " + err.Error())
}
}
}
【问题讨论】:
-
主线程需要等待goroutines完成,所以很明显
wg.Add(1)在你的尝试中放错了位置。 -
谢谢@Inian!应该在哪里?
-
通常,对
Add的调用是立即在启动goroutine 之前,该goroutine 最终会调用Done。这确保了Add调用发生在Wait调用之前,如果您在goroutine 中调用它,则无法保证。 -
知道了!如果我将
Add(1)移动到循环中,它会做同样的事情。 -
请注意,您可以进行微小的优化:您知道
for ... range urls将运行len(urls)次。如果它每次在循环内调用wg.Add(1),您可以让它在循环上方一次读取wg.Add(len(urls))。唯一的小缺陷是,如果循环发生了变化,以至于它实际上并没有在每个 URL 上调用一次go getPage,则您必须将wg.Add移回循环中,或者以其他方式修改len(urls)参数.