Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

第 74 期 time.Timer 源码分析 (Go 1.14) #541

Closed
changkun opened this issue Dec 30, 2019 · 1 comment
Closed

第 74 期 time.Timer 源码分析 (Go 1.14) #541

changkun opened this issue Dec 30, 2019 · 1 comment
Assignees
Labels
Go 夜读 Go 夜读:主题分享 已分享 ✅ Go 夜读的分享状态:分享已完成。

Comments

@changkun
Copy link
Member

changkun commented Dec 30, 2019

【Go 夜读】time.Timer 源码分析 (Go 1.14)

time 是一个很有意思的包,除去需要获取当前时间的 Now 这一平淡无奇、直接对系统调用进行封装( runtime.nanotime )的函数外,其中最有意思的莫过于它所提供的 Timer 和 Ticker 了。他们的实现,驱动了诸如 time.After, time.AfterFunc, time.Tick, time.Sleep 等方法。

即将发布的 Go 1.14 将为 Timer 及其相关依赖带来大幅性能,本次分享我们就来详细分析以下 Go
1.14 中 time.Timer 的源码及其演进过程。

大纲

  • 调度器与调度循环
  • Timer 状态机
  • Timer 的启动、终止与重置
  • Timer 的触发时机
  • Go 1.10 以前以及 Go 1.10 的 Timer 实现

分享时间

2020-01-02 21:00 UTC+8

分享地址

https://zoom.us/j/6923842137

Slides

Google Slides

参考资料

@changkun changkun added Go 夜读 Go 夜读:主题分享 已排期 🗓️ Go 夜读的分享状态:已确定分享时间。 labels Dec 30, 2019
@changkun changkun self-assigned this Dec 30, 2019
@yangwenmai yangwenmai added 已分享 ✅ Go 夜读的分享状态:分享已完成。 and removed 已排期 🗓️ Go 夜读的分享状态:已确定分享时间。 labels Jan 5, 2020
@changkun
Copy link
Member Author

changkun commented Jan 5, 2020

Q: timer 太多会不会阻塞?影响相应时间?

当 Timer 数量达到一定数量之后,自然一定会影响执行时间,导致执行被延后,本质上取决于调度器的性能。请参考此程序的性能:

// +build darwin

package main

// 执行结果
//
// Go 1.13
// =======
//
// nG: 1,        nM: 6->7,  min delay: 1.211071ms,  max delay: 1.211071ms,    avg delay: 1.211071ms
// nG: 10,       nM: 6->9,  min delay: 2.43905ms,   max delay: 2.508454ms,    avg delay: 2.475065ms
// nG: 100,      nM: 6->13, min delay: 2.882342ms,  max delay: 3.313549ms,    avg delay: 2.868386ms
// nG: 1000,     nM: 6->16, min delay: 1.621052ms,  max delay: 2.260831ms,    avg delay: 1.764849ms
// nG: 10000,    nM: 6->26, min delay: 5.217981ms,  max delay: 21.298197ms,   avg delay: 5.91939ms
// nG: 100000,   nM: 6->27, min delay: 10.541721ms, max delay: 198.380399ms,  avg delay: 22.10622ms
// nG: 1000000,  nM: 6->27, min delay: 5.756865ms,  max delay: 2.02314083s,   avg delay: 222.923253ms
// nG: 10000000, nM: 6->20, min delay: 1s,          max delay: 38.582967061s, avg delay: 26.504949572s
//
// Go 1.14
// =======
//
// nG: 1,       nM: 6->7,  min delay: 869.498µs,   max delay: 869.498µs,    avg delay: 869.498µs
// nG: 10,      nM: 6->7,  min delay: 594.135µs,   max delay: 643.668µs,    avg delay: 632.559µs
// nG: 100,     nM: 6->10, min delay: 4.857182ms,  max delay: 5.159684ms,   avg delay: 4.91121ms
// nG: 1000,    nM: 6->14, min delay: 5.04654ms,   max delay: 5.353962ms,   avg delay: 4.72734ms
// nG: 10000,   nM: 6->15, min delay: 2.34473ms,   max delay: 17.550883ms,  avg delay: 2.260576ms
// nG: 100000,  nM: 6->15, min delay: 8.236635ms,  max delay: 150.083108ms, avg delay: 16.357226ms
// nG: 1000000, nM: 6->16, min delay: 96.257176ms, max delay: 387.506452ms, avg delay: 231.691528ms
// nG: 10000000,nM: 6->17, min delay: 97.696762ms, max delay: 9.239115867s, avg delay: 3.949883253s

import (
	"fmt"
	"os"
	"os/exec"
	"runtime/trace"
	"strconv"
	"strings"
	"sync"
	"time"
)

var (
	minDelay time.Duration = time.Second
	avgDelay time.Duration
	maxDelay time.Duration

	thStart int
	thStop  int

	nM = fmt.Sprintf("ps M %d | wc -l", os.Getpid())
	nG = 100
)

func main() {
	thStart, err := getThreads()
	if err != nil {
		panic(err)
	}

	f, _ := os.Create("trace.out")
	defer f.Close()
	trace.Start(f)
	defer trace.Stop()

	// 让所有 goroutine 都休眠到当前的五秒钟以后
	t := time.Now()
	run := t.Add(time.Second * 5)

	wg := sync.WaitGroup{}
	wg.Add(nG)
	for i := 0; i < nG; i++ {
		go func() {
			// 计算时间误差,将 timer 设置为修正后的时间
			remain := run.Sub(time.Now())
			if remain > 0 {
				timer := time.NewTimer(remain)
				<-timer.C
			}

			// 统计
			delay := time.Now().Sub(run)
			if delay < minDelay {
				minDelay = delay
			}
			if delay > maxDelay {
				maxDelay = delay
			}
			avgDelay += delay
			wg.Done()
		}()
	}
	wg.Wait()

	thStop, err = getThreads()
	if err != nil {
		panic(err)
	}
	fmt.Printf("nG: %v, nM: %v->%v, min delay: %v, max delay: %v, avg delay: %v\n", nG, thStart, thStop, minDelay, maxDelay, avgDelay/time.Duration(nG))
}

// getThreads returns the number of running threads
func getThreads() (int, error) {
	out, err := exec.Command("bash", "-c", nM).Output()
	if err != nil {
		return 0, fmt.Errorf("M: failed to fetch #threads: %v", err)
	}
	n, err := strconv.Atoi(strings.TrimSpace(string(out)))
	if err != nil {
		return 0, fmt.Errorf("M: failed to parse #threads: %v", err)
	}
	return n, nil
}

Q: 官网才到 1.13 版本,哪里获取的1.14版本呢?

github.com/golang/go master 分支上的代码为最近提交的源代码,目前 1.14beta1 已出,可以通过 repo 的 tag 来定位发布的代码。

Q: Go 1.14 可能存在什么问题?

scavenger 存在潜在的性能问题,参见:golang/go#35788

Q: 源码中有 wall clock reading 和 monotonic clock reading 的概念,它们有什么区别吗?

monotonic clock: 程序启动后到当前的时间(单调递增)
wall clock: 进程运行的时间总量, 包含了进程在阻塞和等待状态的时间(不一定单调)

Q: timer 现在主流的应用场景在哪?

定时任务、延迟执行等等。

Q: Github 的代码跳转是什么插件?

GitHub 原生支持 Go 代码跳转,无需插件

Q: syscall 的调用次数图是用什么工具生成的?

go tool trace

Q: chrome collection 用的什么?

Toby

Q: 关于 runtime 的源码,是不是从最早版本读容易些,想了解一下 go 的调度策略以及 gc

不一定,但不建议从早期版本开始读。早期版本已经经历过很多迭代,从早期版本看起的工作量巨大。

Q: 对于源码的学习,有什么好的建议吗?

最重要的其实是过滤掉读源码时候的噪音,很多源代码跟主要的执行逻辑没有抢相关,而是为了支持其他功能而添加的,学会鉴别这类代码很重要。

Q: 源码从哪里切入比较好?

任何位置都是可以的,取决于个人阅读的兴趣点。我个人是从 runtime/proc.go 的调度器开始的。

Q: 频繁调用sleep ,频繁的调用系统调用内核 sleep ,M和tp要剥离开P,那这样岂不是会导致系统线程暴涨?1.13的版本

会,但不会过分暴涨。因为执行 timerproc 的 M 在被解绑后,会在 tp 执行完毕后 M 会被其他的 P 抢占。

Q: 对于GMP有个地方我不太明白,说GMP与linux的任务管控不一样,因为后者是抢占式的,task是有权重的。那么Processor执行Goroutine的时候,也应该有个类似时间片的概念吧,就是我执行一段时间之后,我就先暂停当前go routine的上下文,把其放到p 中g队列的末尾。还是就是一直执行,通过work steal来保证每个p 队列的g不会“滞留”过久

Go 1.14 中 G 会被异步抢占,当 G 执行时间过长会被中断信号中断,进而调度其他的 G。在之前是依靠工作窃取的随机化来尽力保证 G 不会滞留过久。

@yangwenmai yangwenmai added this to Plan in Go 夜读分享计划 via automation Feb 13, 2020
@yangwenmai yangwenmai moved this from Plan to Done in Go 夜读分享计划 Feb 13, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Go 夜读 Go 夜读:主题分享 已分享 ✅ Go 夜读的分享状态:分享已完成。
Projects
Development

No branches or pull requests

2 participants