user3147268
user3147268

Reputation: 1884

Goroutines with ListenAndServe increases performance?

I'm not very familiar with Go's routines but since I'm working with the router of net/http I saw a few times thatListenAndServe() is wrapped by a go routine.

A server needs to be able to handle multiple requests simultaneously out of the box to be efficient. So why are go routines as 'lightweight threads' used? Does the concurrency provide any advantages?

Here's an example by OpenShift

package main

import (
    "fmt"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hello OpenShift!")
}

func main() {
    http.HandleFunc("/", helloHandler)

    go func() {
        fmt.Println("serving on 8080")
        err := http.ListenAndServe(":8080", nil)
        if err != nil {
            panic("ListenAndServe: " + err.Error())
        }
    }()

    go func() {
        fmt.Println("serving on 8888")
        err := http.ListenAndServe(":8888", nil)
        if err != nil {
            panic("ListenAndServe: " + err.Error())
        }
    }()
    select {}
}

Upvotes: 21

Views: 15122

Answers (3)

Andy Parinas
Andy Parinas

Reputation: 71

I don't think you need a go routine to start ListenAndServe. According to the Go Documentations.

"ListenAndServe calls Serve". Serve is go routine.

ListenAndServe listens on the TCP network address addr and then calls Serve with handler to handle requests on incoming connections. Accepted connections are configured to enable TCP keep-alives. Handler is typically nil, in which case the DefaultServeMux is used. https://golang.org/pkg/net/http/#ListenAndServe

func Serve(l net.Listener, handler Handler) error Serve accepts incoming HTTP connections on the listener l, creating a new service goroutine for each. The service goroutines read requests and then call handler to reply to them. Handler is typically nil, in which case the DefaultServeMux is used. https://golang.org/pkg/net/http/#Serve

Upvotes: 0

Rob Napier
Rob Napier

Reputation: 299355

http.ListenAndServe is a blocking call. If you want to go one doing more work (like making a second http.ListenAndServe call), you need to move it over to a separate goroutine. That's all they're doing here.

They're using select{} at the end to block the main goroutine, since all their calls to http.ListenAndServe are on other goroutines. If they didn't call select{}, the program would terminate because main() would return.

They could have achieved the same thing by dropping select{}, and removing the go func() wrapper around the last block of code. But I suspect they did it this way so that all the code is consistent.

But this has nothing to do with performance.

In the comments you provided some other examples that are similar. In the first example:

func main() {
    http.HandleFunc("/", responsehandler.Handler)
    go func() {
      http.ListenAndServe(":8888", nil)
    }()
    fileservice.NewWatcher()
}

This calls http.ListenAndServe and then calls fileservice.NewWatcher() (which blocks). If they hadn't wrapped the call in a goroutine, fileservice.NewWatcher() would never have been called.

The other two examples are a common piece of boilerplate:

func init() {
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()
}

This turns on the debug profiler web server. Again, it's a goroutine so that calling init returns immediately rather than blocking. This particular case allows the caller to just import _ "profiling" and "magically" get the debug profiler web server.

Upvotes: 25

Volker
Volker

Reputation: 42413

No it does not have any special benefits beside being "run in the background".

Upvotes: 1

Related Questions