Golang:使用goroutine还是不使用goroutine?

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

Golang: to goroutine or not to goroutine?

问题

在使用Go开发HTTP服务器时,我经常面临这样的困境。

假设我希望尽快向客户端响应http状态码200(然后在后台执行工作),这就是我通常的做法:

我让我的主要HTTP处理程序接收请求,将http 200写入响应,并通过一个通道发送消息(如果我有N个监听该通道的工作程序,我使用一个具有N个缓冲区的缓冲通道):

func myHttpHandler(rw http.ResponseWriter, req *http.Request) {
    rw.WriteHeader(200)
    log(req)
}

func log(req *http.Request) {
    msg := createLog(req)
    if msg != nil {
        channel <- msg
    }
}

我在初始化时启动的监听器将永远监听该通道:

func init() {
    for i := 0; i < workerCount; i++ {
        go worker(i, maxEntrySize, maxBufferSize, maxBufferTime)
    }
}

func worker(workerID int, maxEntrySize int, maxBufferSize int, maxBufferTime time.Duration) {
    for {
        entry := <-channel
        // ...
        // ...
        // ...
    }
}

现在,我的主要问题是:我应该在go程中启动log(req)函数吗?即:

func myHttpHandler(rw http.ResponseWriter, req *http.Request) {
    rw.WriteHeader(200)
    go func() { log(req) }()
}

据我所知,在这种情况下,为每个HTTP请求开启一个go程是没有意义的。

由于log(req)函数的当前操作主要是向通道发送一些数据,该操作非常快速。唯一需要时间的情况是如果通道被阻塞。现在,如果通道被阻塞,这意味着工作程序被阻塞。由于工作程序永远监听通道上的消息,如果工作程序被阻塞,这意味着我的机器真的无法产生更快的输出(工作程序执行一些I/O操作,但是这也非常快,因为I/O每分钟只发生一次)。

此外,由于我有N个工作程序,我用于从处理程序发送消息的通道具有N个缓冲区,因此只有当所有N个工作程序都被阻塞时,通道才会被阻塞。

这样理解正确吗?在调用log(req)时使用go程的利弊是什么?该处理程序每秒接收多达10,000个请求,我猜为每个请求开启一个go程不是一个好主意。

英文:

Many times when developing an http server in Go I have this dilemma.

Assuming I want to respond to the client with http statuscode 200 as fast as possible (and then perform work at the back), this is why I usually do:

I have my main http handler receive the request, I write http 200 to the response, and I send a message over a channel (if I have N workers listening on that channel, I am using a buffered channel of N):

func myHttpHandler(rw http.ResponseWriter, req *http.Request) {
	rw.WriteHeader(200)
	log(req)
}

func log(req *http.Request) {
	msg := createLog(req)
	if msg != nil {
		channel &lt;- msg
	}
}

I have my listeners (fired off on init) listening forever on that channel:

func init() {
	for i := 0; i &lt; workerCount; i++ {
		go worker(i, maxEntrySize, maxBufferSize, maxBufferTime)
	}
}

func worker(workerID int, maxEntrySize int, maxBufferSize int, maxBufferTime time.Duration) {
	for {
		entry := &lt;-channel
		...
		...
		...

Now, my main question is: should I fire off the log(req) function inside a go routine? i.e.

func myHttpHandler(rw http.ResponseWriter, req *http.Request) {
	rw.WriteHeader(200)
	go func() { log(req) } ()
}

As far as I can gather, there's no point in opening up a goroutine for every http request in this case.

Since the current operation of the log(req) function is mostly sending some data over a channel - that operation is super quick. The only time when it's not quick - is if the channel blocks. Now, if the channel blocks, it has to mean that the worker is blocked. And since the worker listens for messages on the channel forever - if the worker is blocked, it means my machine is truly not capable to produce faster output (the worker does some I/O as you can imagine, but that's also very quick, because the I/O only happens once per minute).

Furthermore, since I have N workers, the channel I am using to send the messages on from the handler is buffered with N, so it only blocks if all N workers are blocked.

Is this correct? What are the pros and cons of using a goroutine for the log(req) call? This handler receives upto 10K requests per second, I am guessing it's not a good idea to open a goroutine for each request.

答案1

得分: 5

在这种情况下,为每个HTTP请求打开一个goroutine没有意义。

当你使用net/http的Server时,这已经发生了。你的处理程序会在自己的goroutine中被调用。

我猜为每个请求打开一个goroutine不是一个好主意。

这也不是一个坏主意。Go的运行时可以轻松处理数十万个goroutine。

然而,如果log阻塞,你可能会因为等待接收完整的HTTP响应而超时,而只执行rw.WriteHeader(200)还不足以构成一个完整的响应。

为了解决这个问题,你可以这样做:

if cr, ok := rw.(io.Closer) {
    cr.Close()
}

同时,将响应的Content-Length头设置为0也可能是一个好主意。

英文:

> There's no point in opening up a goroutine for every http request in this case.

That already happens when you use net/http's Server. Your handler is invoked in a goroutine of its own.

> I am guessing it's not a good idea to open a goroutine for each request.

It's not a bad idea either. Go's runtime can easily handle hundreds of thousands of goroutines.

However, if log blocks, you risk timing out on your client, who is waiting to receive a full HTTP response and only doing rw.WriteHeader(200) doesn't constitute one yet.

To remedy this, you can do something like:

if cr, ok := rw.(io.Closer) {
    cr.Close()
}

And it's also probably a good idea to set the Content-Length header of your response to 0.

huangapple
  • 本文由 发表于 2015年9月11日 19:46:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/32522762.html
匿名

发表评论

匿名网友

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

确定