goroutines中请求的时间安排

时间:2015-09-25 10:55:25

标签: go timing goroutine

我正在尝试计算并发请求所需的时间。我的时间结果比ab2报告的结果慢了四倍。

我已经尝试过两种不同的计时请求,这两种方式都会产生类似的结果(远远超过ab2的结果)。举一个例子,ab2将在本地服务器上报告最长2毫秒的请求,而此代码将报告最多4.5毫秒。顺便说一下,整个代码库都可用here

如何正确计时请求?

方法1:时序不仅包括请求

来自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)
}

方法2:使用带有延迟语句的内部函数

来自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,这没有帮助。

1 个答案:

答案 0 :(得分:1)

当你的goroutine进入系统调用(写入套接字)时,它们会被抢占。这意味着他们被打断了,另一个goroutine会在他们的位置上运行。最终,您的抢先goroutine将再次安排,它将继续在它停止的地方运行。但是,这并不一定就在系统调用完成后发生。

goroutine中的时间很难,因为即使你按顺序执行所有操作,Go 1.5的垃圾收集器也会偶尔运行,从而中断理论上的串行循环。

唯一真正的解决方案有点复杂:

  1. 直接使用RawSyscall
  2. 使用//go:nosplit注释您的功能,以防止它被抢占。
  3. 禁用垃圾收集器。
  4. 即便如此,我可能会忘记一些事情。