I am playing around with Go a bit and I've a problem that I am unable to solve.
The following code is the least possible code that reproduces my problem. The goal of the original code is to delegate http request to goroutines. Each goroutine does a bit of heavy image calculations and is supposed to respond.
package main
import (
"fmt"
"runtime"
"net/http"
)
func main() {
http.HandleFunc("/", handle)
http.ListenAndServe(":8080", nil)
}
func handle(w http.ResponseWriter, r *http.Request) {
// the idea is to be able to handle several requests
// in parallel
// the "go" is problematic
go delegate(w)
}
func delegate(w http.ResponseWriter) {
// do some heavy calculations first
// present the result (in the original code, the image)
fmt.Fprint(w, "hello")
}
In the case of a go delegate(w)
I get no response, without the go
it works out nicely.
Can anyone explain what's going on? Thanks a lot!
ListenAndServe
already launches goroutines to call your handler function, so you shouldn't do it yourself.
Here's the code of the relevant functions from the package source :
1089 func ListenAndServe(addr string, handler Handler) error {
1090 server := &Server{Addr: addr, Handler: handler}
1091 return server.ListenAndServe()
1092 }
1010 func (srv *Server) ListenAndServe() error {
1011 addr := srv.Addr
1012 if addr == "" {
1013 addr = ":http"
1014 }
1015 l, e := net.Listen("tcp", addr)
1016 if e != nil {
1017 return e
1018 }
1019 return srv.Serve(l)
1020 }
1025 func (srv *Server) Serve(l net.Listener) error {
1026 defer l.Close()
1027 var tempDelay time.Duration // how long to sleep on accept failure
1028 for {
1057 go c.serve()
1058 }
1059 panic("not reached")
1060 }
579 // Serve a new connection.
580 func (c *conn) serve() {
581 defer func() {
582 err := recover()
669 handler.ServeHTTP(w, w.req)
So your code should simply be
func handle(w http.ResponseWriter, r *http.Request) {
// the idea is to be able to handle several requests
// in parallel
// do some heavy calculations first
// present the result (in the original code, the image)
fmt.Fprint(w, "hello")
}
The handler is already called from an "outer" goroutine (a per request one). The handler must do everything what has to be done, e.g. writing a full response, before it returns. You're returning "prematurely" b/c of a superfluous go statement. Please try simply to put the body of "delegate" in 'handle' and check if that improves something ;-)
Sometimes the Go scheduler can be really unmerciful to Goroutines. The problem is this: you have an applications and you run go routines, so the scheduler thinks: hey, I might actually do some optimizations. Why don't I just run this certain Goroutine later to save some CPU time and make the application more responsive?
This is what happens: in your code is no way of enforcing the Goroutine to finish at some point. In fact the Go documentation says this:
For example, in this program:
var a string func hello() { go func() { a = "hello" }() print(a) }
the assignment to a is not followed by any synchronization event, so it is not guaranteed to be observed by any other goroutine. In fact, an aggressive compiler might delete the entire go statement.
If the effects of a goroutine must be observed by another goroutine, use a synchronization mechanism such as a lock or channel communication to establish a relative ordering.
So the solution to your problem is to add a synchronisation event, e.g. by using a channel:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", handle)
http.ListenAndServe(":8080", nil)
}
func handle(w http.ResponseWriter, r *http.Request) {
// the idea is to be able to handle several requests
// in parallel
// the "go" is problematic...
ch := make(chan int)
go delegate(w, ch)
// ...but not anymore:
<-ch
}
func delegate(w http.ResponseWriter, ch chan<- int) {
// do some heavy calculations first
// present the result (in the original code, the image)
fmt.Fprint(w, "hello")
ch <- 1
}
From: The Go Memory Model
Anyways, as others have pointed out, your example is currently kind of artificial. But there are certainly scenarios in which it makes sense to call other Goroutines from inside an http handler. E.g. if you do heavy calculations and HTTP streaming at the same time, or several heavy calculations at the same time. Although I think in the latter case you would have probably added a channel yourself for synchronization purposes.