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 關注
- 252 瀏覽
添加回答
舉報