并发的HTTP请求没有响应。

huangapple go评论108阅读模式
英文:

concurrent http request gives no response

问题

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

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

  1. package main
  2. import (
  3. "fmt"
  4. "runtime"
  5. "net/http"
  6. )
  7. func main() {
  8. http.HandleFunc("/", handle)
  9. http.ListenAndServe(":8080", nil)
  10. }
  11. func handle(w http.ResponseWriter, r *http.Request) {
  12. // 目标是能够并行处理多个请求
  13. // "go" 关键字是有问题的
  14. go delegate(w)
  15. }
  16. func delegate(w http.ResponseWriter) {
  17. // 首先进行一些复杂的计算
  18. // 返回结果(在原始代码中是图像)
  19. fmt.Fprint(w, "hello")
  20. }

当使用go delegate(w)时,我没有得到任何响应,而不使用go时,一切正常。

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

英文:

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.

  1. package main
  2. import (
  3. "fmt"
  4. "runtime"
  5. "net/http"
  6. )
  7. func main() {
  8. http.HandleFunc("/", handle)
  9. http.ListenAndServe(":8080", nil)
  10. }
  11. func handle(w http.ResponseWriter, r *http.Request) {
  12. // the idea is to be able to handle several requests
  13. // in parallel
  14. // the "go" is problematic
  15. go delegate(w)
  16. }
  17. func delegate(w http.ResponseWriter) {
  18. // do some heavy calculations first
  19. // present the result (in the original code, the image)
  20. fmt.Fprint(w, "hello")
  21. }

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!

答案1

得分: 5

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

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

  1. 1089 func ListenAndServe(addr string, handler Handler) error {
  2. 1090 server := &Server{Addr: addr, Handler: handler}
  3. 1091 return server.ListenAndServe()
  4. 1092 }
  5. 1010 func (srv *Server) ListenAndServe() error {
  6. 1011 addr := srv.Addr
  7. 1012 if addr == "" {
  8. 1013 addr = ":http"
  9. 1014 }
  10. 1015 l, e := net.Listen("tcp", addr)
  11. 1016 if e != nil {
  12. 1017 return e
  13. 1018 }
  14. 1019 return srv.Serve(l)
  15. 1020 }
  16. 1025 func (srv *Server) Serve(l net.Listener) error {
  17. 1026 defer l.Close()
  18. 1027 var tempDelay time.Duration // how long to sleep on accept failure
  19. 1028 for {
  20. 1057 go c.serve()
  21. 1058 }
  22. 1059 panic("not reached")
  23. 1060 }
  24. 579 // Serve a new connection.
  25. 580 func (c *conn) serve() {
  26. 581 defer func() {
  27. 582 err := recover()
  28. 669 handler.ServeHTTP(w, w.req)

所以你的代码应该简单地是:

  1. func handle(w http.ResponseWriter, r *http.Request) {
  2. // 这个想法是能够并行处理多个请求
  3. // 先进行一些繁重的计算
  4. // 展示结果(在原始代码中是图片)
  5. fmt.Fprint(w, "hello")
  6. }
英文:

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 :

  1. 1089 func ListenAndServe(addr string, handler Handler) error {
  2. 1090 server := &Server{Addr: addr, Handler: handler}
  3. 1091 return server.ListenAndServe()
  4. 1092 }
  5. 1010 func (srv *Server) ListenAndServe() error {
  6. 1011 addr := srv.Addr
  7. 1012 if addr == "" {
  8. 1013 addr = ":http"
  9. 1014 }
  10. 1015 l, e := net.Listen("tcp", addr)
  11. 1016 if e != nil {
  12. 1017 return e
  13. 1018 }
  14. 1019 return srv.Serve(l)
  15. 1020 }
  16. 1025 func (srv *Server) Serve(l net.Listener) error {
  17. 1026 defer l.Close()
  18. 1027 var tempDelay time.Duration // how long to sleep on accept failure
  19. 1028 for {
  20. 1057 go c.serve()
  21. 1058 }
  22. 1059 panic("not reached")
  23. 1060 }
  24. 579 // Serve a new connection.
  25. 580 func (c *conn) serve() {
  26. 581 defer func() {
  27. 582 err := recover()
  28. 669 handler.ServeHTTP(w, w.req)

So your code should simply be

  1. func handle(w http.ResponseWriter, r *http.Request) {
  2. // the idea is to be able to handle several requests
  3. // in parallel
  4. // do some heavy calculations first
  5. // present the result (in the original code, the image)
  6. fmt.Fprint(w, "hello")
  7. }

答案2

得分: 1

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

英文:

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 并发的HTTP请求没有响应。

答案3

得分: 1

有时候Go调度器对Goroutines可能会非常无情。问题在于:你有一个应用程序并且运行了一些Goroutines,所以调度器认为:嘿,我可能可以进行一些优化。为什么我不稍后再运行这个特定的Goroutine,以节省一些CPU时间并使应用程序更加响应呢?

事实上,以下是发生的情况:在你的代码中没有强制Goroutine在某个点结束的方法。事实上,Go文档中说过:

例如,在这个程序中:

  1. var a string
  2. func hello() {
  3. go func() { a = "hello" }()
  4. print(a)
  5. }

对a的赋值没有后续的同步事件,因此不能保证被其他Goroutine观察到。事实上,一个积极的编译器可能会删除整个go语句。

如果一个Goroutine的效果必须被另一个Goroutine观察到,使用同步机制,如锁或通道通信来建立相对顺序。

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

package main

  1. import (
  2. "fmt"
  3. "net/http"
  4. )
  5. func main() {
  6. http.HandleFunc("/", handle)
  7. http.ListenAndServe(":8080", nil)
  8. }
  9. func handle(w http.ResponseWriter, r *http.Request) {
  10. // 这个想法是能够同时处理多个请求
  11. // "go"是有问题的...
  12. ch := make(chan int)
  13. go delegate(w, ch)
  14. // ...但现在不再有问题了:
  15. <-ch
  16. }
  17. func delegate(w http.ResponseWriter, ch chan<- int) {
  18. // 首先进行一些繁重的计算
  19. // 展示结果(在原始代码中是图像)
  20. fmt.Fprint(w, "hello")
  21. ch <- 1
  22. }

来自:Go内存模型

无论如何,正如其他人指出的,你的例子目前有点人为。但是肯定存在一些场景,在这些场景中,从http处理程序内部调用其他Goroutines是有意义的。例如,如果你同时进行繁重的计算和HTTP流式传输,或者同时进行多个繁重的计算。尽管我认为在后一种情况下,你可能已经为了同步目的自己添加了一个通道。

英文:

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

  1. import (
  2. &quot;fmt&quot;
  3. &quot;net/http&quot;
  4. )
  5. func main() {
  6. http.HandleFunc(&quot;/&quot;, handle)
  7. http.ListenAndServe(&quot;:8080&quot;, nil)
  8. }
  9. func handle(w http.ResponseWriter, r *http.Request) {
  10. // the idea is to be able to handle several requests
  11. // in parallel
  12. // the &quot;go&quot; is problematic...
  13. ch := make(chan int)
  14. go delegate(w, ch)
  15. // ...but not anymore:
  16. &lt;-ch
  17. }
  18. func delegate(w http.ResponseWriter, ch chan&lt;- int) {
  19. // do some heavy calculations first
  20. // present the result (in the original code, the image)
  21. fmt.Fprint(w, &quot;hello&quot;)
  22. ch &lt;- 1
  23. }

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.

huangapple
  • 本文由 发表于 2012年10月23日 04:05:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/13018962.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定