昨天群里有人问了一个问题

golang defer 函数参数是什么时候求值的哈, 我这边试了下输出有时候两者是一样的,有时候两者相差 1000ms

这里面的 defer 的参数计算问题可以在这篇文章中找到

本文主要讨论 time.Now 函数的精度问题。

先看一下下面这段代码

package main

import (
	"fmt"
	"time"
)

func test() {
	for i := 0; i <= 1000; i++ {
		start := time.Now()                                              // 1
		defer fmt.Print(time.Now().Nanosecond()-start.Nanosecond(), ",") // 2
		time.Sleep(1000)                                                 // 3
	}
	fmt.Println()
}

func test2() {
	s := time.Now() //4
	N := 1000
	for i := 0; i <= N; i++ {
		time.Now() // 5
	}
	fmt.Println()
	fmt.Println((time.Now().Nanosecond() - s.Nanosecond()) / (N + 1)) // 6
}

func main() {
	test()
	test2()
}

test 函数就是截图里面那位朋友的代码。

首先,line 2 肯定是先计算 time.Now(),然后再将参数入栈的,现在的问题是:这样的代码的结果应该是固定的,为什么有时候返回 0ns,有时候返回 1000ns

我看到这段代码后,想到的第一个原因就是 time.Now 这个函数的执行时间导致的上面的问题,但是仔细一想:如果是这样的话,name 应该每次相差的结果(line 2 print 的)应该差不多呀,为什么大部分是 0,少部分是 1000 呢?而且为什么是 1000,不是 2000 呢?

然后我就翻了一下 gotime.Now 的源码:

//go:linkname time_now time.now
func time_now() (sec int64, nsec int32, mono int64) {
	sec, nsec = walltime()
	return sec, nsec, nanotime()
}
//go:nosplit
//go:cgo_unsafe_args
func walltime() (int64, int32) {
	var t timeval
	libcCall(unsafe.Pointer(funcPC(walltime_trampoline)), unsafe.Pointer(&t))
	return int64(t.tv_sec), 1000 * t.tv_usec  // line 7
}

[捂脸]根据 line 7,在 darwin(mac) 系统上,go 的 time.Now 精度就是 1000ns。

所以在上面 line 2 的代码中,每个 time.Now 都是 80 - 150 ns 的时间,然后 print 0,到 1000 ns 过去后,就 print 一个 1000 ns

将上面的代码交叉编译并在 linux 系统上执行,结果是:

嗯...是正常的了。