在 Goroutines 中比较时间

huangapple go评论83阅读模式
英文:

Comparing times in Goroutines

问题

我有一个用Go语言编写的测试:

func TestThings(t *testing.T) {
    tCh := make(chan int64, 10000)
    ctx, cx := context.WithTimeout(context.Background(), 20*time.Second)
    defer cx()

    wg := sync.WaitGroup{}
    wg.Add(2)
    go func(c context.Context) {
        defer wg.Done()
        defer close(tCh)
        for {
            select {
            case <-c.Done():
                return
            default:
                nt := time.Now()
                tCh <- nt.UnixNano()
            }
        }
    }(ctx)

    go func(c context.Context) {
        defer wg.Done()
        for {
            select {
            case <-c.Done():
                return
            default:
                v := <-tCh
                nt := time.Now()
                res := nt.UnixNano() - v
                if res < 0 {
                    t.Errorf("got less than 0; diff: %d now: %d then: %d", res, nt.UnixNano(), v)
                }
            }
        }
    }(ctx)
    wg.Wait()
}

运行这个测试有时会得到以下结果:

go test -v -run TestThings ./test
=== RUN   TestThings
    test.go:48: got less than 0; diff: -33686100 now: 1639183246323013700 then: 1639183246356699800
    test.go:48: got less than 0; diff: -33535000 now: 1639183246323171300 then: 1639183246356706300
    test.go:48: got less than 0; diff: -33490200 now: 1639183246323222600 then: 1639183246356712800
    test.go:48: got less than 0; diff: -33488300 now: 1639183246323231000 then: 1639183246356719300
    test.go:48: got less than 0; diff: -33502600 now: 1639183246323241000 then: 1639183246356743600
    test.go:48: got less than 0; diff: -33551600 now: 1639183246323249100 then: 1639183246356800700
         ...
--- FAIL: TestThings (20.01s)
FAIL
FAIL    test 20.022s
FAIL

为什么接收 Goroutine 中的时间有时会早于发送 Goroutine 中的时间?

我知道在计算机中,时钟通常是困难且不可靠的,但我期望由于这些 Goroutine(也许?)在同一个进程中,但肯定在同一台主机上,调用 time.Now() 应该返回递增的值。或者至少不是"更早"的值。

更新:

可能值得一提的另一件事:我在Macbook Pro上的VSCode devcontainer中运行这个测试。

英文:

I have a test written in Go:

func TestThings(t *testing.T) {
	tCh := make(chan int64, 10000)
	ctx, cx := context.WithTimeout(context.Background(), 20*time.Second)
	defer cx()

	wg := sync.WaitGroup{}
	wg.Add(2)
	go func(c context.Context) {
		defer wg.Done()
		defer close(tCh)
		for {
			select {
			case &lt;-c.Done():
				return
			default:
				nt := time.Now()
				tCh &lt;- nt.UnixNano()
			}
		}
	}(ctx)

	go func(c context.Context) {
		defer wg.Done()
		for {
			select {
			case &lt;-c.Done():
				return
			default:
				v := &lt;-tCh
				nt := time.Now()
				res := nt.UnixNano() - v
				if res &lt; 0 {
					t.Errorf(&quot;got less than 0; diff: %d now: %d then: %d&quot;, res, nt.UnixNano(), v)
				}
			}
		}
	}(ctx)
	wg.Wait()
}

Running this test (sometimes) ends with the result:

go test -v -run TestThings ./test
=== RUN   TestThings
    test.go:48: got less than 0; diff: -33686100 now: 1639183246323013700 then: 1639183246356699800
    test.go:48: got less than 0; diff: -33535000 now: 1639183246323171300 then: 1639183246356706300
    test.go:48: got less than 0; diff: -33490200 now: 1639183246323222600 then: 1639183246356712800
    test.go:48: got less than 0; diff: -33488300 now: 1639183246323231000 then: 1639183246356719300
    test.go:48: got less than 0; diff: -33502600 now: 1639183246323241000 then: 1639183246356743600
    test.go:48: got less than 0; diff: -33551600 now: 1639183246323249100 then: 1639183246356800700
         ...
--- FAIL: TestThings (20.01s)
FAIL
FAIL    test 20.022s
FAIL

Why are the times in the receiving Goroutine sometimes before the times in the sending Goroutine?

I understand that clocks are generally hard and unreliable when it comes to computers, but I would expect since these Goroutines are (maybe?) in the same process, but definitely on the same host, that calls to time.Now() would return ever increasing values. Or at least not "earlier" ones.

Update:

Something else possibly worth mentioning: I'm running this inside a VSCode devcontainer on a Macbook Pro.

答案1

得分: 0

根据原始问题中@Peter的评论,引用了time包文档time.Time的序列化设计上会丢失任何单调信息:

因为单调时钟读数在当前进程之外没有意义,所以由t.GobEncodet.MarshalBinaryt.MarshalJSONt.MarshalText生成的序列化形式会省略单调时钟读数,而t.Format不提供其格式。同样,构造函数time.Datetime.Parsetime.ParseInLocationtime.Unix,以及解组函数t.GobDecodet.UnmarshalBinaryt.UnmarshalJSONt.UnmarshalText总是创建没有单调时钟读数的时间。

将原始问题中的代码片段更改为在Goroutine之间传递time.Time值没有问题。

英文:

As commented by @Peter on the original question referencing the time package docs, serialization of time.Time loses any monotonic information by design:

> Because the monotonic clock reading has no meaning outside the current
> process, the serialized forms generated by t.GobEncode, t.MarshalBinary,
> t.MarshalJSON, and t.MarshalText omit the monotonic clock reading, and
> t.Format provides no format for it. Similarly, the constructors time.Date,
> time.Parse, time.ParseInLocation, and time.Unix, as well as the unmarshalers
> t.GobDecode, t.UnmarshalBinary. t.UnmarshalJSON, and t.UnmarshalText always
> create times with no monotonic clock reading.

Changing the code snippet from the original question to pass time.Time values between the Goroutines works without issue.

huangapple
  • 本文由 发表于 2021年12月11日 10:00:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/70312103.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定