time.now() 测出的耗时比实际网络延迟高,因其记录的是含调度抢占、gc stw、函数调用开销等非网络因素的挂钟时间,而非纯网络耗时。

time.Now() 测出的耗时为什么比实际网络延迟高?
因为 time.Now() 记录的是“挂钟时间”,它包含调度抢占、GC STW、函数调用开销等非网络因素。在并发 HTTP 请求中,一个 goroutine 发起 http.Get() 后可能被 runtime 抢占,等它重新调度回来再调用 time.Now(),中间那几毫秒就被算进去了——这不是代码写错了,是 Go 运行时为吞吐和公平性做的取舍。
- 本地 loopback 环境下,
ab测出 P99 是 2ms,而你的 goroutine 里用time.Now().Sub(start)却得到 4.5ms,大概率就是调度延迟吃掉了 - GC 的 STW 阶段(尤其在标记终止 phase)哪怕只有几百微秒,也会被完整计入
time.Since()结果 - 用
defer func() { end := time.Now(); log(end.Sub(start)) }()这种闭包方式,还会引入额外栈帧和变量捕获,进一步模糊采样点
压测工具场景:用 LockOSThread + 禁用 GC 提升精度
如果你在写类似 wrk 或自研压测器,目标是逼近真实网络延迟,可以临时隔离运行时干扰。这不是常规业务逻辑该做的事,但对短时、可控的基准测试有效。
- 调用
runtime.LockOSThread()将当前 goroutine 绑定到独占 OS 线程,避免被调度器来回迁移 - 用
debug.SetGCPercent(-1)临时停用 GC,记得用defer debug.SetGCPercent(100)恢复 -
time.Now()仍用,但此时它的偏差主要来自系统调用本身,而非 Go runtime 干预 - 注意:切勿在长期运行的服务中这么做,GC 停用会导致内存持续增长直至 OOM
流式命令执行时如何避免计时污染?
用 os/exec.Command 跑外部命令(如 curl、ping)并统计耗时,常见错误是用 cmd.Output() —— 它会把 stdout/stderr 全部缓冲到内存,直到命令退出才返回,导致 time.Now() 测的是“启动+缓冲+解析”总耗时,不是命令真实运行时间。
- 正确做法是直接重定向:
cmd.Stdout = os.Stdout、cmd.Stderr = os.Stderr - 用
cmd.Run()启动,start := time.Now()放在Run()前,time.Since(start)放在Run()后 - 这样测出的就是子进程从
fork到exit的真实生命周期,不含 Go 层缓冲开销 - 如果命令本身有输出缓冲(比如检测到非 TTY 就行缓存),需加
-u或stdbuf -oL控制
倒计时或周期任务为什么越跑越不准?
直接用 time.Sleep(1 * time.Second) 循环减数,误差会累积。因为 time.Sleep 只保证“至少睡够”,不保证“准时醒来”,加上计算、打印、GC 等操作,每轮可能多耗 5–20ms,10 轮下来就偏了 100ms 以上。
- 锚定绝对截止时间:
deadline := time.Now().Add(10 * time.Second) - 每次用
time.Until(deadline)获取剩余时间,它自动处理 NTP 校正、系统时间跳变 - 休眠间隔设小一点,比如
time.Sleep(100 * time.Millisecond),避免漏掉边界时刻 - 别裸跑 goroutine:用
context.Context或 done channel 控制生命周期,否则countdown(5)返回后 goroutine 还在后台挂着
高精度从来不是单靠 time.Now() 的纳秒单位就能解决的;它取决于你是否清楚每一毫秒花在哪——是网络、调度、GC,还是你自己的逻辑。真正难的不是写对一行代码,而是判断此刻该信哪个时间源。











