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通过该通道。
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)
}
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
}
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
}
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[:]
}
TA贡献1829条经验 获得超7个赞
您可以添加两个 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
}
这应该是安全的……也许吧。
- 6 回答
- 0 关注
- 162 浏览
添加回答
举报