图解 Go语言 time.Sleep 的实现原理

科技   2024-08-12 08:51   北京  

▲ 点击上方"网管叨bi叨"关注公众号


我们写程序的时候,一定遇到过需要让程序休眠一段时间再执行的场景,这个时候我们一般会想到用 Sleep 方法,Java 语言有Thread.Sleep, PHP应该是有一个sleep函数,同样的 Go 语言有内置的 time.Sleep 方法。

这篇文章我们来简单梳理一下,Go语言的time.Sleep是怎么实现程序的休眠和唤醒的。

在Go的程序中,使用time.Sleep(d duration)方法时,它会阻塞所在 Goroutine的执行直到d时间结束。

它的实现原理肯定是跟Go语言的GMP模型有关,当执行到time.Sleep时Go调度器让 G 与协同线程M解绑,等时间到了之后再让 G 与 M 进行绑定继续执行后面的任务,可是面试的时候如果只回答道这个程度,感觉还是会被发好人牌--回家等消息吧。

接下来我们把整个过程详细梳理一下,首先来复习一下Go语言调度器的G-M-P模型

  • G 表示一个 Goroutine,对于Go调度器来说,每个G都是一个待执行的任务。
  • P 表示Go调度器中的处理器,它是系统线程和Goroutine 的中间层
  • M 表示的是操作系统的线程,由操作系统进行调度和管理

调度器中的处理器 P 会负责把等待队列中的Goroutine调度到M上去执行,它也能在 Goroutine 进行一些诸如 I/O 这样的操作时让 Goroutine 及时让出线程资源。

通过处理器 P 的调度,每一个线程都能够执行多个 Goroutine,提高线程的利用率。G的调度过程可以用下面这张图表示:

  • 图中的 runq是P持有的等待执行的队列
  • G 被调度到 M 上后,M 的 curg 属性表示当前正在执行的G。
  • g0 是 M 上持有调度栈的 特殊的Goroutine,它会深度参与运行时的调度过程。
  • 我们假设 GT 为代码中有time.Sleep的 G, 它前面的G1已经被调度到了M上去执行。

GT 前面的 G1 执行完成与M解绑后,调度器 P 继续进行调度,此时GT 处于等待队列的队头,所以它被调度到 M 上去执行。当执行到了代码中的time.Sleep时,GT会与 M 解绑,同时用GT的Sleep方法中指定的时间加上其他一些信息会生成Timer 记录到P上,Timer中会包含唤起GT时要调用的 goready 方法。

定时器在 P 中的结构为一个四叉堆,最早到时的定时器的放在堆顶上,这个数据结构我没研究过,懂的老哥可以在评论区里补充。

GT 与 M 的解绑是通过调用gopark方法完成的,它释放当前Goroutine与 M 的连接后,该Goroutine脱离当前的 M 被挂起,进入Gwaiting状态

此时GT不在运行队列上,调度器会调度一个新的Goroutine G3 到 M 上执行。G3执行完或者是也被挂起后,调度器会进行下一轮调度,这时假设 P 检查自己记录的定时器时发现GT的定时器到时间了,是时候把GT重新唤起来了。

唤起GT 是通过执行创建定时器时指定的 goready 方法, 把 GT 插入到P的等待队列的头部。

回到P 执行队列头部的GT 会被马上调度到 M 上去执行。

以上就是Go代码中遇到time.Sleep后的整个调度过程,有兴趣深入研究的,可以对照着这里总结的步骤一步步地去看源码。

公众号图片上传后就不是高清的了,想要文中知识点的高清无水印大图的可以使用以下获取方式:

  • 关注公众号「网管叨bi叨」,在公众号消息框中回复关键字:面试速记


最后在结尾推荐一下我专栏--程序员的全能画图课,专栏有针对大型项目的技术评审案例分析和带练演示,无论是小型项目和大型项目都通过实际的案例分析讲解带你掌握下面这些技能。关于技术博文、公众号的画图经验诀窍也在专栏中有所涉及


课程采用理解优于记忆的方式,用平时开发中常见的案例分析,教会大家对他们的使用。同时还会普及一些做好业务开发的经验要诀,教大家怎么对业务结构和流程进行多视角的分析和可视化表达。

现在可在公众号专栏《程序员的全能画图课》上直接订阅或者扫描上方海报二维码订阅,课程内容已经更新完成,不存在烂尾的风险,后面会定期加更一些新的总结。

网管叨bi叨
分享软件开发和系统架构设计基础、Go 语言和Kubernetes。
 最新文章