-
白板的微信
在此版本中,通道ch有足够的空间,以便在相应的通道读取器不存在的情况下,例程可以推送到它而不会阻塞。package mainimport ( "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/5KUeaUS2FLgcontext此版本说明了附加到取消请求的实现。package mainimport ( "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通过该通道。
-
白猪掌柜的
如果您的目标是只读取一个结果,然后取消其他请求,请尝试如下操作: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)}
-
一只萌萌小番薯
您太早关闭通道,这就是为什么您会看到此错误,最好仅当您不再向通道写入任何内容时才关闭通道,为此您可以使用sync.WaitGroup,如下所示:package mainimport ( "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}
-
繁花不似锦
您最后推荐的代码仅在您的至少一个调用成功时才有效。如果您进行的每个 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}
-
MM们
您的代码将在收到第一个响应后返回。然后关闭通道,让其他 go 例程在关闭的通道上发送。与其返回第一个响应,不如返回一组响应,并以与 url 相同的长度排序,这可能更合适。由于 http 请求可能会出错,因此明智的做法是返回一组错误。package mainimport ( "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[:]}操场
-
千巷猫影
您可以添加两个 goroutine:接收所有请求,发送第一个要返回的请求并丢弃后续请求。当 WaitGroup 完成时,它会关闭您的第一个通道。一个等待 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}这应该是安全的……也许吧。