猿问

如何返回第一个http响应来回答

我使用多个 goroutine 来运行任务,当其中一个完成时,返回并关闭通道,这将导致恐慌:在关闭的通道上发送。


参见代码:


func fetch(urls []string) *http.Response {

    ch := make(chan *http.Response)

    defer close(ch)

    for _, url := range urls {

        go func() {

            resp, err := http.Get(url)

            if err == nil {

                ch <- resp

            }

        }()

    }

    return <-ch

}


如果不关闭通道是没有问题的,但是我觉得不太好,那么有什么优雅的解决方案吗?


感谢您的所有回答,这是我的最终代码:


func fetch(urls []string) *http.Response {

    var wg sync.WaitGroup

    ch := make(chan *http.Response)

    wg.Add(len(urls))

    for _, url := range urls {

        go func(url string) {

            defer wg.Done()

            resp, err := http.Get(url)

            if err == nil {

                ch <- resp

            }

        }(url)

    }

    go func() {

        wg.Wait()

        close(ch)

    }()

    return <-ch

}


元芳怎么了
浏览 163回答 6
6回答

白板的微信

在此版本中,通道ch有足够的空间,以便在相应的通道读取器不存在的情况下,例程可以推送到它而不会阻塞。package mainimport (&nbsp; &nbsp; "fmt"&nbsp; &nbsp; "net/http"&nbsp; &nbsp; "sync")func main() {&nbsp; &nbsp; urls := []string{"", "", ""}&nbsp; &nbsp; res := fetch(urls)&nbsp; &nbsp; fmt.Println(res)}func fetch(urls []string) *http.Response {&nbsp; &nbsp; var wg sync.WaitGroup&nbsp; &nbsp; ch := make(chan *http.Response, len(urls))&nbsp; &nbsp; for _, url := range urls {&nbsp; &nbsp; &nbsp; &nbsp; wg.Add(1)&nbsp; &nbsp; &nbsp; &nbsp; url := url&nbsp; &nbsp; &nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; defer wg.Done()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; req, err := http.NewRequest(http.MethodGet, url, nil)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if err != nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resp, err := http.DefaultClient.Do(req)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if err != nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if resp != nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch <- resp // no need to test the context, ch has rooms for this push to happen anyways.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }()&nbsp; &nbsp; }&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; wg.Wait()&nbsp; &nbsp; &nbsp; &nbsp; close(ch)&nbsp; &nbsp; }()&nbsp; &nbsp; return <-ch}https://play.golang.org/p/5KUeaUS2FLgcontext此版本说明了附加到取消请求的实现。package mainimport (&nbsp; &nbsp; "context"&nbsp; &nbsp; "fmt"&nbsp; &nbsp; "net/http"&nbsp; &nbsp; "sync")func main() {&nbsp; &nbsp; ctx, cancel := context.WithCancel(context.Background())&nbsp; &nbsp; defer cancel()&nbsp; &nbsp; cancel()&nbsp; &nbsp; urls := []string{"", "", ""}&nbsp; &nbsp; res := fetch(ctx, urls)&nbsp; &nbsp; fmt.Println(res)}func fetch(ctx context.Context, urls []string) *http.Response {&nbsp; &nbsp; var wg sync.WaitGroup&nbsp; &nbsp; ch := make(chan *http.Response, len(urls))&nbsp; &nbsp; for _, url := range urls {&nbsp; &nbsp; &nbsp; &nbsp; if ctx.Err() != nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break // break asap.&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; wg.Add(1)&nbsp; &nbsp; &nbsp; &nbsp; url := url&nbsp; &nbsp; &nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; defer wg.Done()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if err != nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resp, err := http.DefaultClient.Do(req)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if err != nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if resp != nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch <- resp // no need to test the context, ch has rooms for this push to happen anyways.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }()&nbsp; &nbsp; }&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; wg.Wait()&nbsp; &nbsp; &nbsp; &nbsp; close(ch)&nbsp; &nbsp; }()&nbsp; &nbsp; return <-ch}https://play.golang.org/p/QUOReYrWqDp友情提醒,不要试图太聪明,使用a sync.WaitGroup,用最简单的逻辑编写流程并让它流动,直到您可以安全地close通过该通道。

白猪掌柜的

如果您的目标是只读取一个结果,然后取消其他请求,请尝试如下操作:func fetch(urls []string) *http.Response {&nbsp; &nbsp; ch := make(chan *http.Response)&nbsp; &nbsp; defer close(ch)&nbsp; &nbsp; ctx, cancel := context.WithCancel(context.Background())&nbsp; &nbsp; defer cancel()&nbsp; &nbsp; for _, url := range urls {&nbsp; &nbsp; &nbsp; &nbsp; go func(ctx context.Context, url string) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resp, err := http.Do(req)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if err == nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case ch <- resp:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case <- ctx.Done():&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }(ctx, url)&nbsp; &nbsp; }&nbsp; &nbsp; return <-ch}这使用了可取消的上下文,因此一旦返回第一个结果,其余的 http 请求就会发出中止信号。注意:您的代码有一个错误,我已在上面修复:func _, url := range urls {&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; http.Do(url) // `url` is changed here on each iteration through the for loop, meaning you will not be calling the url you expect&nbsp; &nbsp; }()}通过传递url给 goroutine 函数来修复此问题,而不是使用闭包:func _, url := range urls {&nbsp; &nbsp; go func(url string) {&nbsp; &nbsp; &nbsp; &nbsp; http.Do(url) // `url` is now safe&nbsp; &nbsp; }(url)}

一只萌萌小番薯

您太早关闭通道,这就是为什么您会看到此错误,最好仅当您不再向通道写入任何内容时才关闭通道,为此您可以使用sync.WaitGroup,如下所示:package mainimport (&nbsp; &nbsp; "fmt"&nbsp; &nbsp; "net/http"&nbsp; &nbsp; "sync")func main() {&nbsp; &nbsp; ch := fetch([]string{"http://github.com/cn007b", "http://github.com/thepkg"})&nbsp; &nbsp; fmt.Println("\n", <-ch)&nbsp; &nbsp; fmt.Println("\n", <-ch)}func fetch(urls []string) chan *http.Response {&nbsp; &nbsp; ch := make(chan *http.Response, len(urls))&nbsp; &nbsp; wg := sync.WaitGroup{}&nbsp; &nbsp; wg.Add(len(urls))&nbsp; &nbsp; for _, url := range urls {&nbsp; &nbsp; &nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; defer wg.Done()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resp, err := http.Get(url)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if err == nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch <- resp&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }()&nbsp; &nbsp; }&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; wg.Wait()&nbsp; &nbsp; &nbsp; &nbsp; close(ch)&nbsp; &nbsp; }()&nbsp; &nbsp; return ch}另外,为了提供带有响应的切片,您可以执行以下操作:func fetch2(urls []string) (result []*http.Response) {&nbsp; &nbsp; ch := make(chan *http.Response, len(urls))&nbsp; &nbsp; wg := sync.WaitGroup{}&nbsp; &nbsp; wg.Add(len(urls))&nbsp; &nbsp; for _, url := range urls {&nbsp; &nbsp; &nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; defer wg.Done()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resp, err := http.Get(url)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if err == nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch <- resp&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }()&nbsp; &nbsp; }&nbsp; &nbsp; wg.Wait()&nbsp; &nbsp; close(ch)&nbsp; &nbsp; for v := range ch {&nbsp; &nbsp; &nbsp; &nbsp; result = append(result, v)&nbsp; &nbsp; }&nbsp; &nbsp; return result}

繁花不似锦

您最后推荐的代码仅在您的至少一个调用成功时才有效。如果您进行的每个 HTTP GET 都出现错误,您的函数将永远阻塞。您可以添加第二个渠道来通知您呼叫已完成:func fetch(urls []string) *http.Response {&nbsp; &nbsp; var wg sync.WaitGroup&nbsp; &nbsp; ch := make(chan *http.Response, len(urls))&nbsp; &nbsp; done := make(chan struct{})&nbsp; &nbsp; wg.Add(len(urls))&nbsp; &nbsp; for _, url := range urls {&nbsp; &nbsp; &nbsp; &nbsp; go func(url string) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; defer wg.Done()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resp, err := http.Get(url)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // only put a response into the channel if we didn't get an error&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if err == nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch <- resp&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }(url)&nbsp; &nbsp; }&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; wg.Wait()&nbsp; &nbsp; &nbsp; &nbsp; // inform main routine that all calls have exited&nbsp; &nbsp; &nbsp; &nbsp; done <- struct{}{}&nbsp; &nbsp; &nbsp; &nbsp; close(ch)&nbsp; &nbsp; }()&nbsp; &nbsp; // return either the first response or nil&nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case r := <-ch:&nbsp; &nbsp; &nbsp; &nbsp; return r&nbsp; &nbsp; &nbsp; &nbsp; case <-done:&nbsp; &nbsp; &nbsp; &nbsp; break&nbsp; &nbsp;}&nbsp; &nbsp;// you can do additional error handling here&nbsp; &nbsp;return nil}

MM们

您的代码将在收到第一个响应后返回。然后关闭通道,让其他 go 例程在关闭的通道上发送。与其返回第一个响应,不如返回一组响应,并以与 url 相同的长度排序,这可能更合适。由于 http 请求可能会出错,因此明智的做法是返回一组错误。package mainimport (&nbsp; &nbsp; "fmt"&nbsp; &nbsp; "net/http")func main() {&nbsp; &nbsp; fmt.Println(fetch([]string{&nbsp; &nbsp; &nbsp; &nbsp; "https://google.com",&nbsp; &nbsp; &nbsp; &nbsp; "https://stackoverflow.com",&nbsp; &nbsp; &nbsp; &nbsp; "https://passkit.com",&nbsp; &nbsp; }))}type response struct {&nbsp; &nbsp; key&nbsp; &nbsp; &nbsp; int&nbsp; &nbsp; response *http.Response&nbsp; &nbsp; err&nbsp; &nbsp; &nbsp; error}func fetch(urls []string) ([]*http.Response, []error) {&nbsp; &nbsp; ch := make(chan response)&nbsp; &nbsp; defer close(ch)&nbsp; &nbsp; for k, url := range urls {&nbsp; &nbsp; &nbsp; &nbsp; go func(k int, url string) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; r, err := http.Get(url)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resp := response {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; key:&nbsp; &nbsp; &nbsp; k,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; response: r,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; err:&nbsp; &nbsp; &nbsp; err,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch <- resp&nbsp; &nbsp; &nbsp; &nbsp; }(k, url)&nbsp; &nbsp; }&nbsp; &nbsp; resp := make([]*http.Response, len(urls))&nbsp; &nbsp; respErrors := make([]error, len(urls))&nbsp; &nbsp; for range urls {&nbsp; &nbsp; &nbsp; &nbsp; r := <-ch&nbsp; &nbsp; &nbsp; &nbsp; resp[r.key] = r.response&nbsp; &nbsp; &nbsp; &nbsp; respErrors[r.key] = r.err&nbsp; &nbsp; }&nbsp; &nbsp; return resp[:], respErrors[:]}操场

千巷猫影

您可以添加两个 goroutine:接收所有请求,发送第一个要返回的请求并丢弃后续请求。当 WaitGroup 完成时,它会关闭您的第一个通道。一个等待 WaitGroup 并发送信号以关闭第一个通道。func fetch(urls []string) *http.Response {&nbsp; &nbsp; var wg sync.WaitGroup&nbsp; &nbsp; ch := make(chan *http.Response)&nbsp; &nbsp; for _, url := range urls {&nbsp; &nbsp; &nbsp; &nbsp; wg.Add(1)&nbsp; &nbsp; &nbsp; &nbsp; go func(url string) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resp, err := http.Get(url)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if err == nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch <- resp:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; wg.Done()&nbsp; &nbsp; &nbsp; &nbsp; }(url)&nbsp; &nbsp; }&nbsp; &nbsp; done := make(chan interface{})&nbsp; &nbsp; go func(){&nbsp; &nbsp; &nbsp; &nbsp; wg.Wait()&nbsp; &nbsp; &nbsp; &nbsp; done <- interface{}{}&nbsp; &nbsp; &nbsp; &nbsp; close(done)&nbsp; &nbsp; }&nbsp; &nbsp; out := make(chan *http.Response)&nbsp; &nbsp; defer close(out)&nbsp; &nbsp; go func(){&nbsp; &nbsp; &nbsp; &nbsp; first = true&nbsp; &nbsp; &nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case r <- ch:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if first {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; first = false&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; out <- r&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case <-done:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; close(ch)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }()&nbsp; &nbsp; return <-out}这应该是安全的……也许吧。
随时随地看视频慕课网APP

相关分类

Go
我要回答