环境准备
首先我们将源码克隆(Fork)为自己的个人仓库,只需要在 GitHub 项目主页点击 Fork 按钮,然后输入项目名称点击确认即可。克隆完毕后,可以下载到本地,或者直接在科隆后的 GitHub 仓库主页上点击 Create codespace on master
来创建 Codespace。Codespace 是 GitHub 推出的基于 Azure 云服务的远程编程功能,现在对个人账号开放了,可以试一下。
点击后,浏览器中会打开一个新页面,并会出现在线 VS Code 的界面,然后显示该项目的目录、代码以及终端,如下图。
由于我们本次的目的是解析源码,我们主要将在这上面展现和阅读代码,并不会执行它。
现在,我们可以开始解析源码了。
入口文件
解析源码的一个比较好的手段是找到入口文件(Entry File),相当于是一本书的引言(Introduction)章节,项目的整体结构通常会在入口文件中体现出来。
我们从项目介绍 README.md
文件中可以看到,这个定时任务库的使用方式是 cron.New(cron.WithSeconds())
之类的,也就是 cron.New
方法。因此,我们可以猜测这个方法是在 cron.go
中,我们打开它看一看。
快速扫了一遍之后,我们可以发现这个 New
方法在 113 行,如下图。
仔细看一下,这个方法就是返回了一个 Cron
类的实例指针,中间的 opts ...Option
参数是一种函数式参数(Functional Option)。而实际的代码实现,无非就是构造了一个 Cron
类的实例指针 c
,并对其应用了函数参数,然后返回它。
这样,我们可以判断,真正的定时任务核心逻辑就在 Cron
类中。
不过,无论如何,我们可以确定,入口文件就是 cron.go
。接下来只需要分析这个文件包含的核心模块、逻辑就可以大概理清楚整个项目的源码了。
核心类
那么我们再来看一下核心类 Cron
的构造,看看是否有什么新东西。
在代码中搜索一下可以定位到 Cron
类在第 13 行。
Cron
类有很多属性,包括小写单词表示的私有属性 entries
、chain
、parser
等等,我们暂时还不知道它们各自的含义,不过可以从名称猜测一下。另外,我们还可以看到第 10-12 行的注释描述,意思是 Cron
会追踪 entries
,并执行被 schedule
定义的函数,它可以开始运行、结束运行,以及 entries
也会在运行过程中被检查。一脸懵逼?是的,这些描述虽然长,但并不能完全解释清楚,我们只有继续源码中的细节,才可以了解清楚。
另外,我们还可以在 Cron
类下面发现 3 个接口以及其描述:
ScheduleParser
:定时任务的解析器,可以解析并返回Schedule
实例;Job
:已提交的定时任务作业Schedule
:用于描述作业的运行周期。
其实,这 3 个接口都很重要,我们从它们的所在位置就可以判断出来。
入口方法
在继续探索之前,我们再回忆一下这个定时任务库的使用方法,除了 cron.New
之外,还需要调用 c.Start()
才能正式生效。因此,我们需要仔细看看 Cron
类的 Start
方法。这其实也是核心类的入口方法(Entry Method)。
我们可以在 cron.go
文件中定位到 Start
方法在第 215 行,如下图。
比较有经验的 Go 语言开发工程师应该会注意到,这是一个典型的原子性操作(Atomic Operation)。c.runningMu
是一个 sync.Mutex
实例,可以加锁(Lock);然后 defer c.runningMu.Unlock()
表示函数调用之后会解锁(Unlock),因此保证重复调用该方法的时候不会出现数据竞速(Data Race);if c.running { return }
的方法表示,如果已经开始运行了,就不会再执行,直接返回;c.running
设置运行状态为 true
;最后一行比较关键,go c.run()
表示新起了一个协程(Goroutine)来运行 c.run
方法。因此,我们找到了更核心的方法,run
。接下来的工作就是继续解析它了。
是不是很像玩 RPG 游戏时不断寻找机关,最终在千辛万苦之下可喜可贺进入下一关?
总结
等一下,就这么完结撒花了?我那啥都准备好了,你就让我看这个?
我们在这里暂时打住的主要原因是不想让这篇文章变得又臭又长。因为源码解析通常是一个需要耐心、繁琐枯燥的过程,而这种过程有时会让读者产生抵触情绪。因此,笔者的主要目的是抛砖引玉,将源码解析的一些核心要领用手把手的方式告诉读者,而读者也会根据自己的理解去实际操作,这样学习起来会更快也会更有意思。
现在稍微总结一下这篇文章用到的解析源码技巧:
- 找到入口文件
- 定位核心类
- 解析入口方法