8

我正在玩一些围棋,但我遇到了一个我无法解决的问题。

以下代码是重现我的问题的最不可能的代码。原始代码的目标是将 http 请求委托给 goroutines。每个 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它的效果很好。

谁能解释发生了什么?非常感谢!

4

3 回答 3

5

ListenAndServe已经启动 goroutines 来调用你的处理函数,所以你不应该自己做。

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

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")
}
于 2012-10-22T20:10:28.050 回答
1

处理程序已经从“外部”goroutine(每个请求一个)调用。处理程序必须在返回之前完成所有必须完成的事情,例如编写完整的响应。您正在“过早地”返回多余的 go 语句的 b/c。请尝试简单地将“代表”的主体放在“句柄”中,并检查这是否会有所改善;-)

于 2012-10-22T20:11:06.457 回答
1

有时 Go 调度器对 Goroutine 真的很无情。问题是这样的:你有一个应用程序并且你运行 goroutine,所以调度器认为:嘿,我实际上可能会做一些优化。为什么我不稍后运行这个特定的 Goroutine 来节省一些 CPU 时间并使应用程序响应更快?

这就是发生的事情:在您的代码中无法强制 Goroutine 在某个时候完成。事实上 Go 文档是这样说的:

例如,在这个程序中:

 var a string

 func hello() {
   go func() { a = "hello" }()
   print(a)
 }

对 a 的赋值之后没有任何同步事件,因此不能保证被任何其他 goroutine 观察到。事实上,激进的编译器可能会删除整个 go 语句。

如果一个 goroutine 的效果必须被另一个 goroutine 观察到,那么使用诸如锁或通道通信之类的同步机制来建立相对排序。

因此,您的问题的解决方案是添加一个同步事件,例如通过使用通道:

包主

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 流式传输,或者同时进行多个繁重的计算。尽管我认为在后一种情况下,您可能会自己添加一个频道以用于同步目的。

于 2012-10-22T21:28:52.927 回答