亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

如何返回第一個http響應來回答

如何返回第一個http響應來回答

Go
元芳怎么了 2023-07-31 15:14:59
我使用多個 goroutine 來運行任務,當其中一個完成時,返回并關閉通道,這將導致恐慌:在關閉的通道上發送。參見代碼:func fetch(urls []string) *http.Response {    ch := make(chan *http.Response)    defer close(ch)    for _, url := range urls {        go func() {            resp, err := http.Get(url)            if err == nil {                ch <- resp            }        }()    }    return <-ch}如果不關閉通道是沒有問題的,但是我覺得不太好,那么有什么優雅的解決方案嗎?感謝您的所有回答,這是我的最終代碼:func fetch(urls []string) *http.Response {    var wg sync.WaitGroup    ch := make(chan *http.Response)    wg.Add(len(urls))    for _, url := range urls {        go func(url string) {            defer wg.Done()            resp, err := http.Get(url)            if err == nil {                ch <- resp            }        }(url)    }    go func() {        wg.Wait()        close(ch)    }()    return <-ch}
查看完整描述

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通過該通道。


查看完整回答
反對 回復 2023-07-31
?
白豬掌柜的

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)

}


查看完整回答
反對 回復 2023-07-31
?
一只萌萌小番薯

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

}


查看完整回答
反對 回復 2023-07-31
?
繁花不似錦

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

}


查看完整回答
反對 回復 2023-07-31
?
MM們

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[:]

}

操場


查看完整回答
反對 回復 2023-07-31
?
千巷貓影

TA貢獻1829條經驗 獲得超7個贊

您可以添加兩個 goroutine:

  1. 接收所有請求,發送第一個要返回的請求并丟棄后續請求。當 WaitGroup 完成時,它會關閉您的第一個通道。

  2. 一個等待 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

}

這應該是安全的……也許吧。


查看完整回答
反對 回復 2023-07-31
  • 6 回答
  • 0 關注
  • 252 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號