orcaman
orcaman

Reputation: 6561

Golang: to goroutine or not to goroutine?

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.

Upvotes: 2

Views: 3920

Answers (1)

thwd
thwd

Reputation: 24818

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.

Upvotes: 5

Related Questions