我是 golang
的新手。所以,请把剑留给我(如果可能的话)。
我试图通过学习教程 here 从网络上获取数据
现在,教程进展顺利,但我想检查边缘情况和错误处理(只是为了彻底了解我对这门语言的新学习,不想成为知识不成熟的人) .
这是我的 go-playground code .
在提问之前我看了很多引用资料,比如:
还有一些,但是我想不通。
如果你不想去 Playground (出于人类未知的原因),这是代码:
// MakeRequest : Makes requests concurrently
func MakeRequest(url string, ch chan<- string, wg *sync.WaitGroup) {
start := time.Now()
resp, err := http.Get(url)
defer func() {
resp.Body.Close()
wg.Done()
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
if err != nil {
fmt.Println(err)
panic(err)
}
secs := time.Since(start).Seconds()
body, _ := ioutil.ReadAll(resp.Body)
ch <- fmt.Sprintf("%.2f elapsed with response length: %d %s", secs, len(body), url)
}
func main() {
var wg sync.WaitGroup
output := []string{
"https://www.facebook.com",
"",
}
start := time.Now()
ch := make(chan string)
for _, url := range output {
wg.Add(1)
go MakeRequest(url, ch, &wg)
}
for range output {
fmt.Println(<-ch)
}
fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
}
更新
我将代码更改为(假设)像这样处理 goroutine 中的错误(go-playground here):
func MakeRequest(url string, ch chan<- string, wg *sync.WaitGroup) {
start := time.Now()
resp, err := http.Get(url)
if err == nil {
secs := time.Since(start).Seconds()
body, _ := ioutil.ReadAll(resp.Body)
ch <- fmt.Sprintf("%.2f elapsed with response length: %d %s", secs, len(body), url)
// fmt.Println(err)
// panic(err)
}
defer wg.Done()
}
更新 2:
回答后,我将代码更改为此,它成功地消除了 chan
死锁,但是现在我需要在 main
中处理它:
func MakeRequest(url string, ch chan<- string, wg *sync.WaitGroup) {
defer wg.Done()
start := time.Now()
resp, err := http.Get(url)
if err == nil {
secs := time.Since(start).Seconds()
body, _ := ioutil.ReadAll(resp.Body)
ch <- fmt.Sprintf("%.2f elapsed with response length: %d %s", secs, len(body), url)
// fmt.Println(err)
// panic(err)
}
// defer resp.Body.Close()
ch <- fmt.Sprintf("")
}
难道没有更优雅的方式来处理这个问题吗?
但现在我陷入了僵局。
感谢和问候。
暂时的
(一个 golang 菜鸟)
最佳答案
您正在正确使用 recover。你有两个问题:
您错误地使用了 panic。只有在出现编程错误时才应该 panic 。避免使用 panic,除非你认为关闭程序是对发生的事情的合理 react 。在这种情况下,我只会返回错误,而不是 panic 。
你在 panic 中 panic 。发生的事情是您首先在
panic(err)
处 panic .然后在你的延迟函数中,你在resp.Body.Close()
处 panic .当 http.Get 返回错误时,它返回 nil 响应。这意味着resp.Body.Close()
作用于 nil 值。
处理此问题的惯用方法如下所示:
func MakeRequest(url string, ch chan<- string, wg *sync.WaitGroup) {
defer wg.Done()
start := time.Now()
resp, err := http.Get(url)
if err != nil {
//handle error without panicing
}
// there was no error, so resp.Body is guaranteed to exist.
defer resp.Body.Close()
...
响应更新:如果http.Get()
返回错误,您永远不会在 channel 上发送。在某个时候,除了主 goroutine 之外的所有 goroutine 都停止运行并且主 goroutine 正在等待 <-ch
.由于该 channel 接收永远不会完成,并且 Go 运行时没有其他可调度的东西,它会发生 panic (不可恢复)。
对评论的回应:为确保 channel 不会挂起,您需要进行某种协调以了解消息何时停止发送。这是如何实现的取决于你的真实程序,一个例子不一定能推断出现实。对于此示例,我将在 WaitGroup 完成后简单地关闭 channel 。
func main() {
var wg sync.WaitGroup
output := []string{
"https://www.facebook.com",
"",
}
start := time.Now()
ch := make(chan string)
for _, url := range output {
wg.Add(1)
go MakeRequest(url, ch, &wg)
}
go func() {
wg.Wait()
close(ch)
}()
for val := range ch {
fmt.Println(val)
}
fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
}
关于go - 我如何处理 goroutine 中的 panic ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54559189/