英文:
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 <- msg
}
}
I have my listeners (fired off on init) listening forever on that channel:
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
...
...
...
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
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论