我正在尝试计算发出并发请求所需的时间。我的计时结果比 ab2 慢了大约四倍报告。
我尝试过两种不同的方式来计时请求,这两种方式都会导致类似的结果(与 ab2 的结果相去甚远)。举个例子,ab2 将在本地服务器上报告最长请求持续 2 毫秒,而此代码将报告最多 4.5 毫秒。顺便说一句,整个代码库可用here .
如何在 go 中正确计时请求?
方法一:定时不仅仅包括请求
来自 this commit .
// Let's spawn all the requests, with their respective concurrency.
wg.Add(r.Repeat)
r.doneWg.Add(r.Repeat)
for rno := 1; rno <= r.Repeat; rno++ {
go func(no int, greq goreq.Request) {
r.ongoingReqs <- struct{}{} // Adding sentinel value to limit concurrency.
startTime := time.Now()
greq.Uri = r.URL.Generate()
gresp, err := greq.Do()
if err != nil {
log.Critical("could not send request to #%d %s: %s", no, r.URL, err)
} else if no < r.Repeat {
// We're always using the last response for the next batch of requests.
gresp.Body.Close()
}
<-r.ongoingReqs // We're done, let's make room for the next request.
resp := Response{Response: gresp, duration: time.Now().Sub(startTime)}
// Let's add that request to the list of completed requests.
r.doneChan <- &resp
runtime.Gosched()
}(rno, greq)
}
方法二:使用带defer语句的内部函数
来自 this commit .
// Let's spawn all the requests, with their respective concurrency.
wg.Add(r.Repeat)
r.doneWg.Add(r.Repeat)
for rno := 1; rno <= r.Repeat; rno++ {
go func(no int, greq goreq.Request) {
r.ongoingReqs <- struct{}{} // Adding sentinel value to limit concurrency.
greq.Uri = r.URL.Generate()
var duration time.Duration
gresp, err := func(dur *time.Duration) (gresp *goreq.Response, err error) {
defer func(startTime time.Time) { *dur = time.Now().Sub(startTime) }(time.Now())
return greq.Do()
}(&duration)
if err != nil {
log.Critical("could not send request to #%d %s: %s", no, r.URL, err)
} else if no < r.Repeat {
// We're always using the last response for the next batch of requests.
gresp.Body.Close()
}
<-r.ongoingReqs // We're done, let's make room for the next request.
resp := Response{Response: gresp, duration: duration}
// Let's add that request to the list of completed requests.
r.doneChan <- &resp
runtime.Gosched()
}(rno, greq)
}
我看过this question , 这没有帮助。
最佳答案
当您的 goroutines 进入系统调用(写入套接字)时,它们会被抢占。这意味着它们被打断了,另一个 goroutine 将在它们的位置运行。最终,你被抢占的 goroutine 将被再次调度,并从中断的地方继续运行。不过,这不一定恰好在系统调用完成后发生。
在 goroutine 中计时是很困难的,因为即使你串行地做所有事情,Go 1.5 的垃圾收集器也会偶尔运行,打断你理论上的串行循环。
唯一真正的解决方案有点复杂:
- 使用RawSyscall直接。
- 使用
//go:nosplit
注释您的函数,以防止它被抢占。 - 禁用垃圾收集器。
即便如此,我也可能会忘记一些事情。
关于go - goroutines中的请求时间,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32780612/