Golang的HTTP超时和goroutine积累

huangapple go评论94阅读模式
英文:

golang http timeout and goroutines accumulation

问题

我使用goroutines实现了http.Get的超时功能,然后我发现goroutines的数量一直在稳步增长,当达到大约1000个左右时,程序就会退出。

代码:

package main

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

// 超时拨号器
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) {
	// 修改拨号器以支持超时和禁用长连接
	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

英文:

I use goroutines achieve http.Get timeout, and then I found that the number has been rising steadily goroutines, and when it reaches 1000 or so, the program will exit

Code:

package main
import (
&quot;errors&quot;
&quot;io/ioutil&quot;
&quot;log&quot;
&quot;net&quot;
&quot;net/http&quot;
&quot;runtime&quot;
&quot;time&quot;
)
// 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 &amp;&amp; disable keep-alive
tr := &amp;http.Transport{
Dial:              timeoutDialler(3 * time.Second),
DisableKeepAlives: true,
}
client := &amp;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 &lt;- Response{[]byte{}, err}
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
ch &lt;- Response{[]byte{}, err}
return
}
tr.CloseIdleConnections()
ch &lt;- Response{body, err}
}()
select {
case &lt;-time.After(5 * time.Second):
return []byte{}, errors.New(&quot;timeout&quot;)
case response := &lt;-ch:
return response.resp, response.err
}
}
func handler(w http.ResponseWriter, r *http.Request) {
_, err := timeoutHttpGet(&quot;http://google.com&quot;)
if err != nil {
log.Println(err)
return
}
}
func main() {
go func() {
for {
log.Println(runtime.NumGoroutine())
time.Sleep(500 * time.Millisecond)
}
}()
s := &amp;http.Server{
Addr:         &quot;:8888&quot;,
ReadTimeout:  15 * time.Second,
WriteTimeout: 15 * time.Second,
}
http.HandleFunc(&quot;/&quot;, handler)
log.Fatal(s.ListenAndServe())
}

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

答案1

得分: 3

将你的chan初始化为1而不是0:

ch := make(chan Response, 1)

并且删除关闭和将ch设置为nil的延迟块。

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

以下是我认为正在发生的情况:

  1. 在5秒超时后,timeoutHttpGet返回
  2. 延迟语句运行,关闭ch并将其设置为nil
  3. 开始执行实际获取操作的go例程完成,并尝试将其数据发送到ch
  4. 但是ch为nil,因此不会接收任何内容,阻止该语句完成,从而阻止go例程完成

我猜你将ch = nil是因为在没有这个之前,当你尝试向关闭的通道写入时会出现运行时恐慌,正如规范所描述的那样。

给ch一个缓冲区为1意味着获取go例程可以发送给它而不需要接收者。如果处理程序由于超时而返回,所有内容将稍后被垃圾回收。

英文:

Init your chan with 1 instead of 0:

ch := make(chan Response, 1)

And remove the defer block that closes and nils ch.

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

Here is what I think is happening:

  1. after the 5s timeout, timeoutHttpGet returns
  2. the defer statement runs, closing ch and then setting it to nil
  3. the go routine it started to do the actual fetch finishes and attempts to send its data to ch
  4. but ch is nil, and so won't receive anything, preventing that statement from finishing, and thus preventing the go routine from finishing

I assume you are setting ch = nil because before you had that, you would get run-time panics because that's what happens when you attempt to write to a closed channel, as described by the spec.

Giving ch a buffer of 1 means that the fetch go routine can send to it without needing a receiver. If the handler has returned due to timeout, everything will just get garbage collected later on.

huangapple
  • 本文由 发表于 2014年1月8日 16:25:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/20990332.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定