为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
元芳怎么了 2023-07-31 15:14:59
我使用多个 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}
查看完整描述

6 回答

?
白板的微信

TA贡献1883条经验 获得超3个赞

在此版本中,通道ch有足够的空间,以便在相应的通道读取器不存在的情况下,例程可以推送到它而不会阻塞。


package main


import (

    "fmt"

    "net/http"

    "sync"

)


func main() {

    urls := []string{"", "", ""}

    res := fetch(urls)

    fmt.Println(res)

}

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

    var wg sync.WaitGroup

    ch := make(chan *http.Response, len(urls))


    for _, url := range urls {

        wg.Add(1)

        url := url

        go func() {

            defer wg.Done()

            req, err := http.NewRequest(http.MethodGet, url, nil)

            if err != nil {

                return

            }

            resp, err := http.DefaultClient.Do(req)

            if err != nil {

                return

            }

            if resp != nil {

                ch <- resp // no need to test the context, ch has rooms for this push to happen anyways.

            }

        }()

    }


    go func() {

        wg.Wait()

        close(ch)

    }()


    return <-ch

}

https://play.golang.org/p/5KUeaUS2FLg

context此版本说明了附加到取消请求的实现。

package main


import (

    "context"

    "fmt"

    "net/http"

    "sync"

)


func main() {

    ctx, cancel := context.WithCancel(context.Background())

    defer cancel()

    cancel()

    urls := []string{"", "", ""}

    res := fetch(ctx, urls)

    fmt.Println(res)

}


func fetch(ctx context.Context, urls []string) *http.Response {

    var wg sync.WaitGroup

    ch := make(chan *http.Response, len(urls))

    for _, url := range urls {

        if ctx.Err() != nil {

            break // break asap.

        }

        wg.Add(1)

        url := url

        go func() {

            defer wg.Done()

            req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)

            if err != nil {

                return

            }

            resp, err := http.DefaultClient.Do(req)

            if err != nil {

                return

            }

            if resp != nil {

                ch <- resp // no need to test the context, ch has rooms for this push to happen anyways.

            }

        }()

    }

    go func() {

        wg.Wait()

        close(ch)

    }()

    return <-ch

}

https://play.golang.org/p/QUOReYrWqDp


友情提醒,不要试图太聪明,使用a sync.WaitGroup,用最简单的逻辑编写流程并让它流动,直到您可以安全地close通过该通道。


查看完整回答
反对 回复 2023-07-31
?
白猪掌柜的

TA贡献1893条经验 获得超10个赞

如果您的目标是只读取一个结果,然后取消其他请求,请尝试如下操作:


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

    ch := make(chan *http.Response)

    defer close(ch)

    ctx, cancel := context.WithCancel(context.Background())

    defer cancel()

    for _, url := range urls {

        go func(ctx context.Context, url string) {

            req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)

            resp, err := http.Do(req)

            if err == nil {

                select {

                case ch <- resp:

                case <- ctx.Done():

                }

            }

        }(ctx, url)

    }

    return <-ch

}

这使用了可取消的上下文,因此一旦返回第一个结果,其余的 http 请求就会发出中止信号。


注意:您的代码有一个错误,我已在上面修复:


func _, url := range urls {

    go func() {

        http.Do(url) // `url` is changed here on each iteration through the for loop, meaning you will not be calling the url you expect

    }()

}

通过传递url给 goroutine 函数来修复此问题,而不是使用闭包:


func _, url := range urls {

    go func(url string) {

        http.Do(url) // `url` is now safe

    }(url)

}


查看完整回答
反对 回复 2023-07-31
?
一只萌萌小番薯

TA贡献1795条经验 获得超7个赞

您太早关闭通道,这就是为什么您会看到此错误,

最好仅当您不再向通道写入任何内容时才关闭通道,为此您可以使用sync.WaitGroup,如下所示:


package main


import (

    "fmt"

    "net/http"

    "sync"

)


func main() {

    ch := fetch([]string{"http://github.com/cn007b", "http://github.com/thepkg"})

    fmt.Println("\n", <-ch)

    fmt.Println("\n", <-ch)

}


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

    ch := make(chan *http.Response, len(urls))

    wg := sync.WaitGroup{}

    wg.Add(len(urls))

    for _, url := range urls {

        go func() {

            defer wg.Done()

            resp, err := http.Get(url)

            if err == nil {

                ch <- resp

            }

        }()

    }

    go func() {

        wg.Wait()

        close(ch)

    }()

    return ch

}

另外,为了提供带有响应的切片,您可以执行以下操作:


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

    ch := make(chan *http.Response, len(urls))

    wg := sync.WaitGroup{}

    wg.Add(len(urls))

    for _, url := range urls {

        go func() {

            defer wg.Done()

            resp, err := http.Get(url)

            if err == nil {

                ch <- resp

            }

        }()

    }

    wg.Wait()

    close(ch)

    for v := range ch {

        result = append(result, v)

    }

    return result

}


查看完整回答
反对 回复 2023-07-31
?
繁花不似锦

TA贡献1851条经验 获得超4个赞

您最后推荐的代码仅在您的至少一个调用成功时才有效。如果您进行的每个 HTTP GET 都出现错误,您的函数将永远阻塞。您可以添加第二个渠道来通知您呼叫已完成:


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

    var wg sync.WaitGroup

    ch := make(chan *http.Response, len(urls))

    done := make(chan struct{})

    wg.Add(len(urls))

    for _, url := range urls {

        go func(url string) {

            defer wg.Done()

            resp, err := http.Get(url)

            // only put a response into the channel if we didn't get an error

            if err == nil {

                ch <- resp

            }

        }(url)

    }

    go func() {

        wg.Wait()

        // inform main routine that all calls have exited

        done <- struct{}{}

        close(ch)

    }()


    // return either the first response or nil

    select {

        case r := <-ch:

        return r

        case <-done:

        break

   }


   // you can do additional error handling here

   return nil

}


查看完整回答
反对 回复 2023-07-31
?
MM们

TA贡献1886条经验 获得超2个赞

您的代码将在收到第一个响应后返回。然后关闭通道,让其他 go 例程在关闭的通道上发送。


与其返回第一个响应,不如返回一组响应,并以与 url 相同的长度排序,这可能更合适。


由于 http 请求可能会出错,因此明智的做法是返回一组错误。


package main


import (

    "fmt"

    "net/http"

)


func main() {

    fmt.Println(fetch([]string{

        "https://google.com",

        "https://stackoverflow.com",

        "https://passkit.com",

    }))

}


type response struct {

    key      int

    response *http.Response

    err      error

}


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

    ch := make(chan response)

    defer close(ch)

    for k, url := range urls {

        go func(k int, url string) {

            r, err := http.Get(url)

            resp := response {

                key:      k,

                response: r,

                err:      err,

            }

            ch <- resp

        }(k, url)

    }


    resp := make([]*http.Response, len(urls))

    respErrors := make([]error, len(urls))


    for range urls {

        r := <-ch

        resp[r.key] = r.response

        respErrors[r.key] = r.err

    }

    return resp[:], respErrors[:]

}

操场


查看完整回答
反对 回复 2023-07-31
?
千巷猫影

TA贡献1829条经验 获得超7个赞

您可以添加两个 goroutine:

  1. 接收所有请求,发送第一个要返回的请求并丢弃后续请求。当 WaitGroup 完成时,它会关闭您的第一个通道。

  2. 一个等待 WaitGroup 并发送信号以关闭第一个通道。

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

    var wg sync.WaitGroup

    ch := make(chan *http.Response)

    for _, url := range urls {

        wg.Add(1)

        go func(url string) {

            resp, err := http.Get(url)          

            if err == nil {

                ch <- resp:

            }

            wg.Done()

        }(url)

    }

    done := make(chan interface{})

    go func(){

        wg.Wait()

        done <- interface{}{}

        close(done)

    }


    out := make(chan *http.Response)

    defer close(out)

    go func(){

        first = true

        for {

            select {

            case r <- ch:

                if first {

                    first = false

                    out <- r

                }

            case <-done:

                close(ch)

                return

            }

        }

    }()


    return <-out

}

这应该是安全的……也许吧。


查看完整回答
反对 回复 2023-07-31
  • 6 回答
  • 0 关注
  • 127 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信