并发http请求没有响应

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.