Go 如何将第一个http响应返回到应答
我使用多个goroutine来运行任务,当其中一个任务完成时,返回并关闭通道,这将导致恐慌:在关闭的通道上发送 见代码: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例程在关闭的通道上发送 与其返回第一个响应,
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,我不知道您如何选择“完成”情况,因为取消只会在选择语句之后调用。