2

私は goroutines を使用して http.Get タイムアウトを達成しました。その後、goroutines の数が着実に増加していることがわかりました。1000 程度に達すると、プログラムは終了します。

コード:

package main

import (
        "errors"
        "io/ioutil"
        "log"
        "net"
        "net/http"
        "runtime"
        "time"
)

// timeout dialler
func timeoutDialler(timeout time.Duration) func(network, addr string) (net.Conn, error) {
        return func(network, addr string) (net.Conn, error) {
                return net.DialTimeout(network, addr, timeout)
        }
}

func timeoutHttpGet(url string) ([]byte, error) {
        // change dialler add timeout support && disable keep-alive
        tr := &http.Transport{
                Dial:              timeoutDialler(3 * time.Second),
                DisableKeepAlives: true,
        }

        client := &http.Client{Transport: tr}

        type Response struct {
                resp []byte
                err  error
        }

        ch := make(chan Response, 0)
        defer func() {
                close(ch)
                ch = nil
        }()

        go func() {
                resp, err := client.Get(url)
                if err != nil {
                        ch <- Response{[]byte{}, err}
                        return
                }
                defer resp.Body.Close()

                body, err := ioutil.ReadAll(resp.Body)
                if err != nil {
                        ch <- Response{[]byte{}, err}
                        return
                }

                tr.CloseIdleConnections()
                ch <- Response{body, err}
        }()

        select {
        case <-time.After(5 * time.Second):
                return []byte{}, errors.New("timeout")
        case response := <-ch:
                return response.resp, response.err
        }
}

func handler(w http.ResponseWriter, r *http.Request) {
        _, err := timeoutHttpGet("http://google.com")
        if err != nil {
                log.Println(err)
                return
        }
}

func main() {
        go func() {
                for {
                        log.Println(runtime.NumGoroutine())
                        time.Sleep(500 * time.Millisecond)
                }
        }()

        s := &http.Server{
                Addr:         ":8888",
                ReadTimeout:  15 * time.Second,
                WriteTimeout: 15 * time.Second,
        }

        http.HandleFunc("/", handler)
        log.Fatal(s.ListenAndServe())
}

http://play.golang.org/p/SzGTMMmZkI

4

1 に答える 1

3

chan を 0 ではなく 1 で初期化します。

ch := make(chan Response, 1)

そして、ch を閉じて nil する defer ブロックを削除します。

参照: http://blog.golang.org/go-concurrency-patterns-timing-out-and

これが私が起こっていると思うことです:

  1. 5 秒のタイムアウトの後、timeoutHttpGet が返されます
  2. defer ステートメントが実行され、ch が閉じられ、nil に設定されます。
  3. 実際のフェッチを実行するために開始した go ルーチンが終了し、そのデータを ch に送信しようとします
  4. しかし、ch は nil であるため、何も受け取らず、そのステートメントが終了しないため、go ルーチンが終了しません。

仕様ch = nilで説明されているように、閉じたチャネルに書き込もうとすると実行時パニックが発生するため、設定していると思います。

ch に 1 のバッファーを与えるということは、fetch go ルーチンがレシーバーを必要とせずに ch に送信できることを意味します。ハンドラーがタイムアウトのために戻った場合、すべてが後でガベージ コレクションされます。

于 2014-01-08T08:47:15.910 に答える