我对golang
来说还很陌生。因此,请尽量避免使用剑。
我正在尝试通过研究教程here
从网上获取数据现在,该教程进展顺利,但是我想检查一下极端情况和错误处理(只是为了对我的语言有了新的了解,而又不想成为半生半熟的人)
这是我的go-playground code。
在问之前,我看过很多参考文献:
还有一些,但是我想不出来。
这是您不想去操场的代码(出于人类尚不知道的原因):
// 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()
}
回答后,我将代码更改为此,并成功消除了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("")
}
难道没有更优雅的方式来处理此问题吗?
但是现在我陷入了僵局。
感谢和问候。
Temporarya
(golang noobie)
答案 0 :(得分:6)
您正在正确使用恢复。您有两个问题:
您使用的恐慌不正确。仅当出现编程错误时才应惊慌。除非您认为删除程序对所发生的事情有合理的反应,否则请避免使用恐慌。在这种情况下,我只会返回错误,而不是惊慌。
在恐慌中您正在恐慌。发生的事情是您首先对panic(err)
感到恐慌。然后,在延后函数中,您会惊慌于resp.Body.Close()
。当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()
返回错误,则您永远不会在该频道上发送。在某个时候,除主goroutine之外的所有goroutine都停止运行,并且主goroutine正在等待<-ch
。由于该通道的接收将永远不会完成,并且Go运行时没有其他可安排的时间,因此它会慌乱(无法恢复)。
回复评论:为确保频道不挂起,您需要进行某种协调才能知道消息何时停止发送。如何执行此操作取决于您的实际程序,并且示例不一定可以推断到实际情况。对于此示例,我将在WaitGroup完成后直接关闭通道。
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())
}