并发的HTTP请求没有响应

8

我正在尝试使用Go语言,并遇到了一个无法解决的问题。

以下代码是能够重现我的问题的最小代码。原始代码的目标是将http请求委托给goroutine。每个goroutine都会进行一些复杂的图像计算,并应该做出响应。

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")
}

在使用go delegate(w)时,我没有得到任何响应,在去掉go后就可以顺利工作。

有人能解释一下发生了什么吗?非常感谢!

3个回答

5

ListenAndServe 已经启动了goroutine来调用你的处理函数,所以你不需要自己这样做。

以下是包源代码中相关函数的代码

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)

所以你的代码应该简单地是:
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")
}

你似乎是对的,它没有明确写出来。我总觉得这很明显,可能是因为我之前写过http服务器,但文档应该要精确说明。希望会有所改变。甚至连大型官方示例也忘了提到它... - Denys Séguret

1
有时候Go调度程序对Goroutines可能会非常无情。问题在于:您有一个应用程序并运行go例程,因此调度程序认为:嘿,我实际上可以进行一些优化。为什么不稍后运行这个特定的Goroutine以节省一些CPU时间并使应用程序更具响应性呢?
发生的情况是:在您的代码中没有强制执行Goroutine在某个点完成的方法。事实上,Go文档说:

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.

所以解决您的问题的方法是添加同步事件,例如使用通道:

包 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
}

来自:Go内存模型

无论如何,正如其他人所指出的那样,你的例子目前有点不真实。但是,在某些情况下,从http处理程序内部调用其他Goroutine是有意义的。例如,如果您同时进行大量计算和HTTP流式传输,或者同时进行几个重要计算。虽然我认为在后一种情况下,您可能已经添加了一个通道以进行同步。


谢谢,即使我指出了我的示例是故意琐碎的 :). 我在我的原始代码中检查了您的解决方案。尽管所有请求都得到了响应,但部分响应是半个绘制的图像(响应涉及一些图像处理)。只要我接受net / http已经执行了go handle,那么我的go delegate就是多余的go,响应就是正确的。 - ffel

1
处理程序已经从“外部”goroutine(每个请求一个)中调用。 处理程序必须在返回之前完成所有必须完成的工作,例如编写完整响应。 您由于多余的go语句而“过早”返回。 请尝试将“delegate”的主体放入“handle”中,并检查是否有所改善;-)

你说得对,handle 函数中的 delegate 代码确实有效。其他人认为我的示例中的 go 是隐含的,所以我的 go 看起来是多余的。谢谢。虽然我在文档中没有找到这个信息。 - ffel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接