Go 如何将第一个http响应返回到应答

Go 如何将第一个http响应返回到应答,go,Go,我使用多个goroutine来运行任务,当其中一个任务完成时,返回并关闭通道,这将导致恐慌:在关闭的通道上发送 见代码: func fetch(URL[]字符串)*http.Response{ ch:=make(chan*http.Response) 延迟关闭(ch) 对于u,url:=范围url{ go func(){ resp,err:=http.Get(url) 如果err==nil{ ch收到第一个响应后,您的代码返回。然后关闭通道,让其他go例程在关闭的通道上发送 与其返回第一个响应,

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

见代码:

func fetch(URL[]字符串)*http.Response{
ch:=make(chan*http.Response)
延迟关闭(ch)
对于u,url:=范围url{
go func(){
resp,err:=http.Get(url)
如果err==nil{

ch收到第一个响应后,您的代码返回。然后关闭通道,让其他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[:]
}
主程序包
进口(
“fmt”
“net/http”
)
func main(){
fmt.Println(获取([]字符串{
"https://google.com",
"https://stackoverflow.com",
"https://passkit.com",
}))
}
类型响应结构{
关键点
响应*http.response
错误
}
func fetch(URL[]字符串)([]*http.Response,[]错误){
ch:=制造(chan响应)
延迟关闭(ch)
对于k,url:=范围url{
go func(k int,url字符串){
r、 错误:=http.Get(url)
resp:=响应{
关键:k,
答复:r,
呃:呃,,
}

ch您过早关闭通道,因此您可以看到此错误,
只有当您不想在通道中写入更多内容时,才最好关闭通道,为此,您可以使用
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
}
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
}
主程序包
进口(
“fmt”
“net/http”
“同步”
)
func main(){
ch:=fetch([]字符串{”http://github.com/cn007b", "http://github.com/thepkg"})

fmt.Println(“\n”,如果您的目标是只读取一个结果,然后取消其他请求,请尝试以下操作:

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
}
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
}
通过将
url
传递到goroutine func,而不是使用闭包来修复此问题:

func _, url := range urls {
    go func(url string) {
        http.Do(url) // `url` is now safe
    }(url)
}

相关帖子:

您可以添加两个goroutine:

  • 接收所有请求,发送第一个要返回的请求并删除以下请求。当WaitGroup完成时,它关闭您的第一个通道
  • 等待WaitGroup并发送关闭第一个通道的信号
  • func fetch(URL[]字符串)*http.Response{
    var wg sync.WaitGroup
    ch:=make(chan*http.Response)
    对于u,url:=范围url{
    工作组.添加(1)
    go func(url字符串){
    resp,err:=http.Get(url)
    如果err==nil{
    
    ch@Clément在Flimzy的答案中检测到一个错误。在封闭通道上写
    错误是很弱的,但他无法生成一个简单正确的版本。Flimzy的版本对早期上下文取消也是很弱的

    这是解决办法

    package main
    
    import (
        "fmt"
    )
    
    func fetch(urls []string) *http.Response {
        var wg sync.WaitGroup
        ch := make(chan *http.Response, len(urls))
        ctx, cancel := context.WithCancel(context.Background())
        defer cancel()
        for _, url := range urls {
            wg.Add(1)
            go func(ctx context.Context, url string) {
                defer wg.Done()
                req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
                resp, err := http.Do(req)
                if err == nil {
                    ch <- resp
                }
            }(ctx, url)
        }
        go func() {
            wg.Wait()
            close(ch)
        }()
    
        return <-ch
    }
    
    主程序包
    进口(
    “fmt”
    )
    func fetch(URL[]字符串)*http.Response{
    var wg sync.WaitGroup
    ch:=make(chan*http.Response,len(URL))
    ctx,cancel:=context.WithCancel(context.Background())
    推迟取消
    对于u,url:=范围url{
    工作组.添加(1)
    go func(ctx context.context,url字符串){
    推迟工作组完成()
    req,u:=http.NewRequestWithContext(ctx,http.MethodGet,url,nil)
    resp,err:=http.Do(req)
    如果err==nil{
    
    ch问题是
    fetch()
    在goroutine完成之前返回,当它返回时,
    延迟关闭(ch)
    执行。您可能需要一个waitgroup。
    返回它可能有意义,比如说如果您只想要第一个响应。不过我也发现它很奇怪,很可能应该返回频道。@Clément:这方面可能有意义,但您需要(优雅地)返回它清理其他的goroutine,这还没有完成。tks,但是代码“wg.done()”似乎丢失了。@liwei2633它在goroutine内部。抱歉,我只是没有看到它,我是瞎子:)@liwei2633还将频道更新为
    ch:=make(chan*http.Response,len(url))
    所以现在它是非阻塞的。我也在考虑这一点,但我不认为这是完全安全的。如果两个请求在到达
    select
    语句之前完成了,或者如果
    fetch
    消耗了通道中的值并关闭了通道,在请求完成并关闭时会发生什么在它到达
    select
    语句之前?@Clément:
    cancel
    应该发生在
    close(ch)
    之前,为了防止出现这种情况,您是对的。我将更新答案。毫无用处的复杂。异步序列
    WaitGroup.Wait()+close(chan)
    是安全的。我也这么认为,但我只是想找到一个安全问题的解决方案。您的解决方案解决了安全问题和复杂性问题。我同意这是正确的,而且更简单,因为少了一个goroutine。尽管:1.您的
    wg.Wait()
    语句可能在任何
    wg.Add(1)之前被阅读执行
    ,这将导致恐慌。您可以在
    for
    语句之后移动该goroutine进行修复。2.您最后一次选择
    将永远不会使用上下文案例,您可以删除此
    选择
    。如果等待()在Add之前调用,它退出,不会死机。但是,在关闭的通道上可能会有写操作。我不同意您对2的看法。是的,在关闭的通道上写操作会死机……对于2,我不知道您如何选择“完成”情况,因为
    取消
    只会在
    选择
    语句之后调用。