6 回答

TA貢獻(xiàn)1883條經(jīng)驗(yàn) 獲得超3個(gè)贊
在此版本中,通道ch有足夠的空間,以便在相應(yīng)的通道讀取器不存在的情況下,例程可以推送到它而不會(huì)阻塞。
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
此版本說明了附加到取消請(qǐng)求的實(shí)現(xiàn)。
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,用最簡(jiǎn)單的邏輯編寫流程并讓它流動(dòng),直到您可以安全地close通過該通道。

TA貢獻(xiàn)1893條經(jīng)驗(yàn) 獲得超10個(gè)贊
如果您的目標(biāo)是只讀取一個(gè)結(jié)果,然后取消其他請(qǐng)求,請(qǐng)嘗試如下操作:
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
}
這使用了可取消的上下文,因此一旦返回第一個(gè)結(jié)果,其余的 http 請(qǐng)求就會(huì)發(fā)出中止信號(hào)。
注意:您的代碼有一個(gè)錯(cuò)誤,我已在上面修復(fù):
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 函數(shù)來修復(fù)此問題,而不是使用閉包:
func _, url := range urls {
go func(url string) {
http.Do(url) // `url` is now safe
}(url)
}

TA貢獻(xiàn)1795條經(jīng)驗(yàn) 獲得超7個(gè)贊
您太早關(guān)閉通道,這就是為什么您會(huì)看到此錯(cuò)誤,
最好僅當(dāng)您不再向通道寫入任何內(nèi)容時(shí)才關(guān)閉通道,為此您可以使用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
}
另外,為了提供帶有響應(yīng)的切片,您可以執(zhí)行以下操作:
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貢獻(xiàn)1851條經(jīng)驗(yàn) 獲得超4個(gè)贊
您最后推薦的代碼僅在您的至少一個(gè)調(diào)用成功時(shí)才有效。如果您進(jìn)行的每個(gè) HTTP GET 都出現(xiàn)錯(cuò)誤,您的函數(shù)將永遠(yuǎn)阻塞。您可以添加第二個(gè)渠道來通知您呼叫已完成:
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貢獻(xiàn)1886條經(jīng)驗(yàn) 獲得超2個(gè)贊
您的代碼將在收到第一個(gè)響應(yīng)后返回。然后關(guān)閉通道,讓其他 go 例程在關(guān)閉的通道上發(fā)送。
與其返回第一個(gè)響應(yīng),不如返回一組響應(yīng),并以與 url 相同的長(zhǎng)度排序,這可能更合適。
由于 http 請(qǐng)求可能會(huì)出錯(cuò),因此明智的做法是返回一組錯(cuò)誤。
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貢獻(xiàn)1829條經(jīng)驗(yàn) 獲得超7個(gè)贊
您可以添加兩個(gè) goroutine:
接收所有請(qǐng)求,發(fā)送第一個(gè)要返回的請(qǐng)求并丟棄后續(xù)請(qǐng)求。當(dāng) WaitGroup 完成時(shí),它會(huì)關(guān)閉您的第一個(gè)通道。
一個(gè)等待 WaitGroup 并發(fā)送信號(hào)以關(guān)閉第一個(gè)通道。
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
}
這應(yīng)該是安全的……也許吧。
- 6 回答
- 0 關(guān)注
- 248 瀏覽
添加回答
舉報(bào)