惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

美团技术团队
N
Netflix TechBlog - Medium
WordPress大学
WordPress大学
云风的 BLOG
云风的 BLOG
J
Java Code Geeks
V
Visual Studio Blog
H
Help Net Security
Engineering at Meta
Engineering at Meta
Hugging Face - Blog
Hugging Face - Blog
Microsoft Security Blog
Microsoft Security Blog
腾讯CDC
博客园 - 【当耐特】
B
Blog
Stack Overflow Blog
Stack Overflow Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
大猫的无限游戏
大猫的无限游戏
GbyAI
GbyAI
博客园 - 司徒正美
博客园 - 叶小钗
Y
Y Combinator Blog
MyScale Blog
MyScale Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
G
Google Developers Blog
酷 壳 – CoolShell
酷 壳 – CoolShell

Razeen`s Blog

Let's Encrypt 推出 Gen Y 根证书架构:揭示 Web PKI 的五大未来趋势 通过 Wi-Fi 自动备份你的 iPhone 到 Nas 管理培训感悟:从技术视角看管理之路 UPS 一拖多:保护你的 NAS 和 Linux 服务器 从影音中心到 AI 助手:我的50款 Homelab 服务清单 从SSL证书有效期将缩短到47天聊开去 部署一个自己的 Running Page 谈谈特斯拉 Model Y 用车一年的感受和费用分析 弃用 Disqus 评论,使用自建 Waline 使用 Prometheus 和 Grafana 搭建你的证书监控面板 服务器迁移记:一次磁盘换板引发的Linux分区与挂载的学习 改善信息来源, 利用 RSS 高效获取资讯 (RSShub + Reeder5 + WeWe RSS) 多种 Docker 镜像拉取解决方案与实践 App分享 | AppCleaner - Mac上的卸载神器 Azure OpenAI API 申请和使用 如何开通 OneKey 虚拟信用卡,并充值消费 如何拥有一个可长期在国内使用的国外手机号码 分享开通 ChatGPT Plus 过程 如何开通 Depay 虚拟信用卡,并充值消费 如何开通欧易Web3钱包, 交易入账 如何5分钟内1块钱注册 ChatGPT 如何注册美区的 Apple ID (2023年/无需科学上网) NAS折腾记(11): NASTool3.0体验和降级 NAS折腾记(10): Docker版本的NASTool配置 NAS折腾记(9): NASTool与微信交互,微信发送消息远程下载电影【多图】 NAS折腾记(8):群晖安装 NASTool 实现影音半自动化【多图】 内网穿透(2):Tailscale 组网实现内网穿透,操作简单,无成本 内网穿透(1):总结了11中内网穿透的方式,总有一种适合你 NAS折腾记(7):从零开始设置(黑)群晖系统 NAS折腾记(6):黑群晖系统安装好后怎么洗白?
Go学习笔记(九) 计时器的生命周期[译]
2020-08-15 · via Razeen`s Blog

Illustration created for “A Journey With Go”, made from the original Go Gopher, created by Renee French.

全文基于GO 1.14

计时器在定时执行一些任务时很有用。Go内部依靠调度器来管理创建的计时器。而Go的调度程序是协作式的调度方式,这会让整个调度看起来比较复杂,因为goroutune必须自己停止(依赖channel阻塞或system call), 或者由调度器自己在某个调度点暂停。

有关抢占的更多信息,建议您阅读作者的文章Go: Goroutine and Preemption.

生命周期

下面是一段简单示例代码:

func main(){
	sigs := make(chan os.Signal,1)
	signal.Notify(sigs,syscall.SIGINT,syscall.SIGTERM)

	time.AfterFunc(time.Second, func() {
		println("done")
	})

	<- sigs
}

计时器创建后,他会保存到一个链接到当前P的计时器内部列表上,下图就是这段代码的表示形式:

有关G,M,P模型的更多信息,建议您阅读作者的文章Go: Goroutine, OS Thread and CPU Management

从图中可以看到,一旦创建了计时器,它就会注册一个内部回调,该内部回调将使用go回调用户函数,并将其转换为goroutine。

然后,将通过调度程序管理计时器。在每一轮调度中,它都会检查计时器是否准备好运行,如果准备就绪,则准备运行。实际上,由于Go调度程序本身不会运行任何代码,因此运行计时器的回调会将其goroutine排队到本地队列中。然后,当调度程序在队列中将其接收时,goroutine将运行。如选图所示:

根据本地队列的大小,计时器运行可能会稍有延迟。不过,由于Go 1.14中的异步抢占,goroutines在运行时间10ms后会被抢占,降低了延迟的可能性。

延迟

为了了解计时器可能存在的延迟,我们创造一个场景:从同一goroutine创建大量计时器。

由于计时器都链接到当前P,因此繁忙的P无法及时运行其链接的计时器。代码如下:

func main(){

	trace.Start(os.Stderr)
	defer trace.Stop()

	sigs := make(chan os.Signal,1)
	signal.Notify(sigs,syscall.SIGINT,syscall.SIGTERM)

	//time.AfterFunc(time.Second, func() {
	//	println("done")
	//})


	var num int64

	for i:=0; i< 1e3 ; i++ {
		time.AfterFunc(time.Second, func() {
			atomic.AddInt64(&num,1)
		})
	}

	t:= 0
	for i:=0;i<1e10; i++ {
		t ++
	}
	_ = t

	<- sigs

	println(num,"timers created,",t,"iterations done")
}

通过go tool trace, 我们可以看到goroutine正在占用处理器:

由于异步抢占的原因,代表正在运行的goroutine图形被分成了大量较小的块。

在这些块中,一个空间看起来比其他空间大。让我们放大一下:

在该计时器需要运行时,就会发生改情况。此时,当前goroutine已被Go调度程序抢占和取代。调度程序将计时器转换为可运行的goroutine,如图所示。

但是,当前线程的Go调度程序并不是唯一运行计时器的调度程序。Go实施了一种计时器窃取策略,以确保在当前P繁忙时可以由另一个P运行计时器。由于异步抢占,它不太可能发生,但是在我们的示例中,由于使用了大量的计时器,它发生了。如下图所示:

如果我们不考虑计时器窃取,将发生以下情况:

持有计时器的所有goroutine都会添加到本地队列中。然后,由于 P之间的窃取,将准确的调度计时器。

所以,由于异步抢占和工作窃取,延迟几乎不可能发生。

原文 Go: Timers’ Life Cycle