golang并发http请求处理

时间:2016-11-15 12:56:44

标签: multithreading go concurrency httpserver

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

  1. 并发请求处理
  2. 我创建了一个服务器并使用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”。 但是如果我删除了Sleep行,我会看到该程序在完成前一个请求之前从未启动过请求(输出为1 2 1 2 1 2 ...)。 所以我不明白,如果他们是并发或不是真的。如果他们是我希望看到一些混乱的印刷品......

    1. 现实生活中的问题
    2. 在真正的处理程序中,我将请求发送到另一台服务器并将答案返回给用户(对请求和答案进行了一些更改,但在想法中它是一种代理)。所有这些当然需要时间和从可以看到的内容(通过向处理程序添加一些打印),请求被逐个处理,它们之间没有并发(我的打印显示请求开始,执行所有步骤,结束,然后才看到一个新的开始......)。 我能做些什么才能使它们真正并发? 将处理程序函数设置为goroutine会产生错误,请求的主体已经关闭。此外,如果已经并发添加更多goroutines将使事情变得更糟。

      谢谢!

3 个答案:

答案 0 :(得分:5)

你的例子很难说出发生了什么。

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

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

答案 1 :(得分:0)

它确实以并发方式提供请求,如源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

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

答案 2 :(得分:0)

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

我遇到了同样的问题,我的客户端代码正在发送&#34; GET&#34;通过XMLHttpRequest请求缓慢的处理程序(我使用了上面发布的类似处理程序代码,超时10秒)。事实证明,这种请求相互阻挡。 JavaScript客户端代码示例:

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

请注意xhr.send()会立即返回,因为这是一次异步调用,但这并不能保证浏览器会发送实际的&#34; GET&#34;马上请求。

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