我正在使用golang创建一个简单的http服务器。我有两个问题,一个是更理论的,另一个是关于真实的程序。
我创建了一个服务器并使用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 ...)。 所以我不明白,如果他们是并发或不是真的。如果他们是我希望看到一些混乱的印刷品......
在真正的处理程序中,我将请求发送到另一台服务器并将答案返回给用户(对请求和答案进行了一些更改,但在想法中它是一种代理)。所有这些当然需要时间和从可以看到的内容(通过向处理程序添加一些打印),请求被逐个处理,它们之间没有并发(我的打印显示请求开始,执行所有步骤,结束,然后才看到一个新的开始......)。 我能做些什么才能使它们真正并发? 将处理程序函数设置为goroutine会产生错误,请求的主体已经关闭。此外,如果已经并发添加更多goroutines将使事情变得更糟。
谢谢!
答案 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)
}
}
提出一个请求
在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;打印)。