繁体   English   中英

golang并发http请求处理

[英]golang concurrent http request handling

我正在使用golang创建一个简单的http服务器。 我有两个问题,一个是理论上的问题,另一个是关于实际程序的问题。

  1. 并发请求处理

我创建了一个服务器并使用s.ListenAndServe()处理请求。 据我了解,这些请求是同时进行的。 我使用一个简单的处理程序来检查它:

func  ServeHTTP(rw http.ResponseWriter, request *http.Request) {  
    fmt.Println("1")  
    time.Sleep(1 * time.Second)       //Phase 2 delete this line
    fmt.Fprintln(rw, "Hello, world.")
    fmt.Println("2")  
}

我看到如果我发送几个请求,我将看到所有的“ 1”出现,而仅一秒钟之后,所有的“ 2”都出现。 但是,如果删除睡眠行,我会看到该程序永远不会在完成上一个请求之前就开始请求(输出为1 2 1 2 1 2 ...)。 所以我不明白它们是否是并发的。 如果他们是我,我希望在印刷品中看到一些混乱……

  1. 现实生活中的问题

在实际的处理程序中,我将请求发送到另一台服务器,然后将答案返回给用户(对请求和答案进行了一些更改,但从思想上讲,这是一种代理)。 所有这一切当然要花费时间,而且从可见的角度(通过向处理程序添加一些打印),请求是一个接一个地处理的,它们之间没有并发性(我的打印向我显示了请求已开始,经过所有步骤,结束,然后我才看到一个新的开始。 如何使它们真正并发?
将处理程序函数设置为goroutine会产生错误,该请求的主体已经关闭。 同样,如果已经并发了,那么添加更多的goroutine只会使情况变得更糟。

谢谢!

您的示例很难说明正在发生的事情。

下面的示例将清楚地说明请求是并行运行的。

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        if len(r.FormValue("case-two")) > 0 {
            fmt.Println("case two")
        } else {
            fmt.Println("case one start")
            time.Sleep(time.Second * 5)
            fmt.Println("case one end")
        }
    })

    if err := http.ListenAndServe(":8000", nil); err != nil {
        log.Fatal(err)
    }
}

http:// localhost:8000发出一个请求

在5秒钟内再次请求http:// localhost:8000?case-two = true

控制台输出将是

case one start
case two
case one end

它确实以并发方式处理请求,如源https://golang.org/src/net/http/server.go#L2293所示

这是一个人为的例子

package main

import (
    "fmt"
    "log"
    "net/http"
    "sync"
    "time"
)

func main() {
    go startServer()
    sendRequest := func() {
        resp, _ := http.Get("http://localhost:8000/")
        defer resp.Body.Close()
    }
    start := time.Now()
    var wg sync.WaitGroup
    ch := make(chan int, 10)
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            sendRequest()
            ch <- n
        }(i)
    }
    go func() {
        wg.Wait()
        close(ch)
    }()

    fmt.Printf("completion sequence :")
    for routineNumber := range ch {
        fmt.Printf("%d ", routineNumber)
    }
    fmt.Println()
    fmt.Println("time:", time.Since(start))
}

func startServer() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(1 * time.Second)
    })
    if err := http.ListenAndServe(":8000", nil); err != nil {
        log.Fatal(err)
    }
}

通过多次运行,可以很容易地看到发送请求的go例程的完成顺序是完全随机的,并且鉴于通道是fifo的事实,我们可以总结出,服务器以并发方式处理了请求,而不管HandleFunc是否睡与否。 (假设所有请求大约在同一时间开始)。

除上述内容外,如果您在HandleFunc中睡了一秒钟,则完成所有10个例程所需的时间始终为1.xxx秒,这进一步表明服务器同时处理请求以及完成所有请求的总时间应该已经超过10秒。

例:

completion sequence :3 0 6 2 9 4 5 1 7 8 
time: 1.002279359s

completion sequence :7 2 3 0 6 4 1 9 5 8 
time: 1.001573873s

completion sequence :6 1 0 8 5 4 2 7 9 3 
time: 1.002026465s

通过不同步进行打印来分析并发性几乎总是不确定的。

当Go同时处理请求时,客户端实际上可能处于阻塞状态(等待第一个请求完成,然后发送第二个请求),然后客户端将看到最初报告的确切行为。

我遇到了同样的问题,我的客户代码通过XMLHttpRequest向慢速处理程序发送了“ GET”请求(我使用了上面发布的类似处理程序代码,超时时间为10秒)。 事实证明,这样的请求相互阻塞。 JavaScript客户端代码示例:

for (var i = 0; i < 3; i++) {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "/slowhandler");
  xhr.send();
}

请注意, xhr.send()将立即返回,因为这是一个异步调用,但这不能保证浏览器会立即发送实际的“ GET”请求。

GET请求需要进行缓存,如果尝试获取相同的URL,则缓存可能(实际上会)影响对服务器的请求方式。 POST请求不会被缓存,因此如果在上面的示例中将"GET"更改为"POST" ,Go服务器代码将显示/slowhandler将同时被触发(您将看到“ 1 1 1 [...暂停。 ..] 2 2 2“打印)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM